diff options
Diffstat (limited to 'src')
418 files changed, 8273 insertions, 2658 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aaaae3a09..3335d3c21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index 77faade17..14ed76295 100644 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -33,10 +33,7 @@ set(blockchain_db_sources set(blockchain_db_headers) -set(blockchain_db_private_headers - blockchain_db.h - lmdb/db_lmdb.h - ) +monero_find_all_headers(blockchain_db_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(blockchain_db ${crypto_private_headers}) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index a84a4148d..ab73e255c 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 74bd72332..263948fa2 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 1d7b10648..db7fa6c7c 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -25,13 +25,6 @@ // 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. -#ifndef _WIN32 -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/mman.h> -#include <fcntl.h> -#endif - #include "db_lmdb.h" #include <boost/filesystem.hpp> @@ -467,7 +460,12 @@ void mdb_txn_safe::allow_new_txns() creation_gate.clear(); } -void lmdb_resized(MDB_env *env) +void mdb_txn_safe::increment_txns(int i) +{ + num_active_txns += i; +} + +void lmdb_resized(MDB_env *env, int isactive) { mdb_txn_safe::prevent_new_txns(); @@ -478,7 +476,11 @@ void lmdb_resized(MDB_env *env) mdb_env_info(env, &mei); uint64_t old = mei.me_mapsize; + if (isactive) + mdb_txn_safe::increment_txns(-1); mdb_txn_safe::wait_no_active_txns(); + if (isactive) + mdb_txn_safe::increment_txns(1); int result = mdb_env_set_mapsize(env, 0); if (result) @@ -496,7 +498,7 @@ inline int lmdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB { int res = mdb_txn_begin(env, parent, flags, txn); if (res == MDB_MAP_RESIZED) { - lmdb_resized(env); + lmdb_resized(env, 1); res = mdb_txn_begin(env, parent, flags, txn); } return res; @@ -506,7 +508,7 @@ inline int lmdb_txn_renew(MDB_txn *txn) { int res = mdb_txn_renew(txn); if (res == MDB_MAP_RESIZED) { - lmdb_resized(mdb_txn_env(txn)); + lmdb_resized(mdb_txn_env(txn), 0); res = mdb_txn_renew(txn); } return res; @@ -1036,8 +1038,9 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, CURSOR(output_txs) CURSOR(output_amounts) - if (tx_output.target.type() != typeid(txout_to_key)) - throw0(DB_ERROR("Wrong output type: expected txout_to_key")); + crypto::public_key output_public_key; + if (!get_output_public_key(tx_output, output_public_key)) + throw0(DB_ERROR("Could not get an output public key from a tx output.")); if (tx_output.amount == 0 && !commitment) throw0(DB_ERROR("RCT output without commitment")); @@ -1065,7 +1068,7 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, else ok.amount_index = 0; ok.output_id = m_num_outputs; - ok.data.pubkey = boost::get < txout_to_key > (tx_output.target).key; + ok.data.pubkey = output_public_key; ok.data.unlock_time = unlock_time; ok.data.height = m_height; if (tx_output.amount == 0) @@ -1267,11 +1270,11 @@ BlockchainLMDB::~BlockchainLMDB() // batch transaction shouldn't be active at this point. If it is, consider it aborted. if (m_batch_active) { - try { batch_abort(); } + try { BlockchainLMDB::batch_abort(); } catch (...) { /* ignore */ } } if (m_open) - close(); + BlockchainLMDB::close(); } BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB() @@ -1293,26 +1296,6 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB() m_hardfork = nullptr; } -void BlockchainLMDB::check_mmap_support() -{ -#ifndef _WIN32 - const boost::filesystem::path mmap_test_file = m_folder / boost::filesystem::unique_path(); - int mmap_test_fd = ::open(mmap_test_file.string().c_str(), O_RDWR | O_CREAT, 0600); - if (mmap_test_fd < 0) - throw0(DB_ERROR((std::string("Failed to check for mmap support: open failed: ") + strerror(errno)).c_str())); - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([mmap_test_fd, &mmap_test_file]() { - ::close(mmap_test_fd); - boost::filesystem::remove(mmap_test_file.string()); - }); - if (write(mmap_test_fd, "mmaptest", 8) != 8) - throw0(DB_ERROR((std::string("Failed to check for mmap support: write failed: ") + strerror(errno)).c_str())); - void *mmap_res = mmap(NULL, 8, PROT_READ, MAP_SHARED, mmap_test_fd, 0); - if (mmap_res == MAP_FAILED) - throw0(DB_ERROR("This filesystem does not support mmap: use --data-dir to place the blockchain on a filesystem which does")); - munmap(mmap_res, 8); -#endif -} - void BlockchainLMDB::open(const std::string& filename, const int db_flags) { int result; @@ -1324,14 +1307,8 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) throw0(DB_OPEN_FAILURE("Attempted to open db, but it's already open")); boost::filesystem::path direc(filename); - if (boost::filesystem::exists(direc)) - { - if (!boost::filesystem::is_directory(direc)) - throw0(DB_OPEN_FAILURE("LMDB needs a directory path, but a file was passed")); - } - else - { - if (!boost::filesystem::create_directories(direc)) + if (!boost::filesystem::exists(direc) && + !boost::filesystem::create_directories(direc)) { throw0(DB_OPEN_FAILURE(std::string("Failed to create directory ").append(filename).c_str())); } @@ -1354,9 +1331,6 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) m_folder = filename; - try { check_mmap_support(); } - catch(...) { MERROR("Failed to check for mmap support, proceeding"); } - #ifdef __OpenBSD__ if ((mdb_flags & MDB_WRITEMAP) == 0) { MCLOG_RED(el::Level::Info, "global", "Running on OpenBSD: forcing WRITEMAP"); @@ -1569,9 +1543,9 @@ void BlockchainLMDB::close() if (m_batch_active) { LOG_PRINT_L3("close() first calling batch_abort() due to active batch transaction"); - batch_abort(); + BlockchainLMDB::batch_abort(); } - this->sync(); + BlockchainLMDB::sync(); m_tinfo.reset(); // FIXME: not yet thread safe!!! Use with care. @@ -1584,7 +1558,7 @@ void BlockchainLMDB::sync() LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); - if (is_read_only()) + if (BlockchainLMDB::is_read_only()) return; // Does nothing unless LMDB environment was opened with MDB_NOSYNC or in part diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 0e6d70039..bdae44948 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -154,6 +154,7 @@ struct mdb_txn_safe static void prevent_new_txns(); static void wait_no_active_txns(); static void allow_new_txns(); + static void increment_txns(int); mdb_threadinfo* m_tinfo; MDB_txn* m_txn; @@ -358,7 +359,6 @@ public: static int compare_string(const MDB_val *a, const MDB_val *b); private: - void check_mmap_support(); void do_resize(uint64_t size_increase=0); bool need_resize(uint64_t threshold_size=0) const; diff --git a/src/blockchain_db/locked_txn.h b/src/blockchain_db/locked_txn.h index 1834f56fb..beab8af21 100644 --- a/src/blockchain_db/locked_txn.h +++ b/src/blockchain_db/locked_txn.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index e36026e07..fe8078d5e 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index ac533596a..8122d9034 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/blockchain_utilities/README.md b/src/blockchain_utilities/README.md index 4a233bcf5..a7d47e233 100644 --- a/src/blockchain_utilities/README.md +++ b/src/blockchain_utilities/README.md @@ -1,6 +1,6 @@ # Monero Blockchain Utilities -Copyright (c) 2014-2020, The Monero Project +Copyright (c) 2014-2022, The Monero Project ## Introduction diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index 99a84606d..b0964e4a3 100644 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index d53251fd3..dee0f7f2a 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index 8c3c3a009..b98a1f8e2 100644 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 87cd7945c..82fe524de 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 8d81ef54d..f8cca638d 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index b1c599f3a..4a91cf7cc 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index 78a662134..05aaf42ee 100644 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 5f5ca6abf..3009b5024 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index 8356ef420..129a9be21 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_utilities.h b/src/blockchain_utilities/blockchain_utilities.h index 035e2397f..47bbb0faf 100644 --- a/src/blockchain_utilities/blockchain_utilities.h +++ b/src/blockchain_utilities/blockchain_utilities.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index b9ef1de7d..606805a08 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index 567505ac1..ce224baa6 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 7050b9ab1..71477912a 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index ff2875a61..0f2776172 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/bootstrap_serialization.h b/src/blockchain_utilities/bootstrap_serialization.h index bcc8c7e15..261810a6b 100644 --- a/src/blockchain_utilities/bootstrap_serialization.h +++ b/src/blockchain_utilities/bootstrap_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt index d2003316d..db8fe5f94 100644 --- a/src/blocks/CMakeLists.txt +++ b/src/blocks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt index 46d6fedf1..665441f62 100644 --- a/src/checkpoints/CMakeLists.txt +++ b/src/checkpoints/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -41,8 +41,7 @@ set(checkpoints_sources set(checkpoints_headers) -set(checkpoints_private_headers - checkpoints.h) +monero_find_all_headers(checkpoints_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(checkpoints ${checkpoints_private_headers}) diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 6b48d8723..27e77cae8 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 029c50548..07daeb4c0 100644 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 99d9bd8bf..b712ee6b1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -58,36 +58,7 @@ endif() set(common_headers) -set(common_private_headers - apply_permutation.h - base58.h - boost_serialization_helper.h - command_line.h - common_fwd.h - dns_utils.h - download.h - error.h - expect.h - http_connection.h - notify.h - pod-class.h - pruning.h - rpc_client.h - scoped_message_writer.h - unordered_containers_boost_serialization.h - util.h - varint.h - i18n.h - password.h - perf_timer.h - spawn.h - stack_trace.h - threadpool.h - updates.h - aligned.h - timings.h - combinator.h - utf8.h) +monero_find_all_headers(common_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(common ${common_private_headers}) diff --git a/src/common/aligned.c b/src/common/aligned.c index e3a607f66..3e33bfa80 100644 --- a/src/common/aligned.c +++ b/src/common/aligned.c @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/aligned.h b/src/common/aligned.h index fed3ccb36..33242a151 100644 --- a/src/common/aligned.h +++ b/src/common/aligned.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h index 03effc50e..ceccdfe96 100644 --- a/src/common/apply_permutation.h +++ b/src/common/apply_permutation.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/base58.cpp b/src/common/base58.cpp index 77b6a642c..f50bd10fb 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/base58.h b/src/common/base58.h index 4a7b02cbc..fa97ab98c 100644 --- a/src/common/base58.h +++ b/src/common/base58.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/boost_serialization_helper.h b/src/common/boost_serialization_helper.h index 4f9cf0518..4a903107f 100644 --- a/src/common/boost_serialization_helper.h +++ b/src/common/boost_serialization_helper.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/combinator.cpp b/src/common/combinator.cpp index cb4fbc908..72b139737 100644 --- a/src/common/combinator.cpp +++ b/src/common/combinator.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/combinator.h b/src/common/combinator.h index ba851bd81..0d35e4786 100644 --- a/src/common/combinator.h +++ b/src/common/combinator.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 42370f543..30ded6f33 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/command_line.h b/src/common/command_line.h index b4619905b..d80a1b0df 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/common_fwd.h b/src/common/common_fwd.h index ac8894ba4..4853c23c9 100644 --- a/src/common/common_fwd.h +++ b/src/common/common_fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index f0b617798..6ab6ff4fe 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 99e91bc54..f9507b42a 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/download.cpp b/src/common/download.cpp index 3dd9c3976..01d4a9aab 100644 --- a/src/common/download.cpp +++ b/src/common/download.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -53,7 +53,7 @@ namespace tools download_thread_control(const std::string &path, const std::string &uri, std::function<void(const std::string&, const std::string&, bool)> result_cb, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress_cb): path(path), uri(uri), result_cb(result_cb), progress_cb(progress_cb), stop(false), stopped(false), success(false) {} - ~download_thread_control() { if (thread.joinable()) thread.detach(); } + ~download_thread_control() { if (thread.joinable()) { thread.detach(); thread = {}; } } }; static void download_thread(download_async_handle control) @@ -293,9 +293,13 @@ namespace tools { boost::lock_guard<boost::mutex> lock(control->mutex); if (control->stopped) + { + control->thread = {}; return true; + } } control->thread.join(); + control->thread = {}; return true; } @@ -305,10 +309,14 @@ namespace tools { boost::lock_guard<boost::mutex> lock(control->mutex); if (control->stopped) + { + control->thread = {}; return true; + } control->stop = true; } control->thread.join(); + control->thread = {}; return true; } } diff --git a/src/common/download.h b/src/common/download.h index 19a52a786..52af4dc6a 100644 --- a/src/common/download.h +++ b/src/common/download.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/error.cpp b/src/common/error.cpp index e091e4478..f5e402ef4 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/common/error.h b/src/common/error.h index 6fef3eb4b..d271517f4 100644 --- a/src/common/error.h +++ b/src/common/error.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/common/expect.h b/src/common/expect.h index 72e4060a7..6f2d8e291 100644 --- a/src/common/expect.h +++ b/src/common/expect.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/http_connection.h b/src/common/http_connection.h index 5ee39cdbb..f8281fe1e 100644 --- a/src/common/http_connection.h +++ b/src/common/http_connection.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/i18n.cpp b/src/common/i18n.cpp index 051220ee1..6d904a2e3 100644 --- a/src/common/i18n.cpp +++ b/src/common/i18n.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/i18n.h b/src/common/i18n.h index 3bf2f8260..1ed78bf8f 100644 --- a/src/common/i18n.h +++ b/src/common/i18n.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/json_util.h b/src/common/json_util.h index 38464f7f1..6d2cfedb4 100644 --- a/src/common/json_util.h +++ b/src/common/json_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/notify.cpp b/src/common/notify.cpp index f31100214..28f38d8c3 100644 --- a/src/common/notify.cpp +++ b/src/common/notify.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/notify.h b/src/common/notify.h index 65d4e1072..7ff721c8a 100644 --- a/src/common/notify.h +++ b/src/common/notify.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/password.cpp b/src/common/password.cpp index 019f04c95..e6dff95ea 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/password.h b/src/common/password.h index 54d97e565..26b6616ab 100644 --- a/src/common/password.h +++ b/src/common/password.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index 898701b2f..30164a557 100644 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // @@ -27,7 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <vector> -#include "misc_os_dependent.h" +#include "time_helper.h" #include "perf_timer.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index fb89b78c0..c6120b06d 100644 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/pod-class.h b/src/common/pod-class.h index 38eae8948..c6fce75e6 100644 --- a/src/common/pod-class.h +++ b/src/common/pod-class.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/pruning.cpp b/src/common/pruning.cpp index 442b24e4e..5cae238ae 100644 --- a/src/common/pruning.cpp +++ b/src/common/pruning.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/pruning.h b/src/common/pruning.h index 3fac3c0fa..6530dcf2e 100644 --- a/src/common/pruning.h +++ b/src/common/pruning.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/rpc_client.h b/src/common/rpc_client.h index 26bcea751..a603a7baf 100644 --- a/src/common/rpc_client.h +++ b/src/common/rpc_client.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/scoped_message_writer.h b/src/common/scoped_message_writer.h index 0a5ebe1c9..cf4e6855f 100644 --- a/src/common/scoped_message_writer.h +++ b/src/common/scoped_message_writer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/sfinae_helpers.h b/src/common/sfinae_helpers.h index e0778b10f..c5974fe82 100644 --- a/src/common/sfinae_helpers.h +++ b/src/common/sfinae_helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/spawn.cpp b/src/common/spawn.cpp index 9a7e75d41..22e840407 100644 --- a/src/common/spawn.cpp +++ b/src/common/spawn.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/spawn.h b/src/common/spawn.h index c90a0f790..f3c8dbb6e 100644 --- a/src/common/spawn.h +++ b/src/common/spawn.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/stack_trace.cpp b/src/common/stack_trace.cpp index 828b8f1c8..130ba4d81 100644 --- a/src/common/stack_trace.cpp +++ b/src/common/stack_trace.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/stack_trace.h b/src/common/stack_trace.h index 57f939b2d..dc8182154 100644 --- a/src/common/stack_trace.h +++ b/src/common/stack_trace.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index edc87fc48..5d2acc0a6 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/threadpool.h b/src/common/threadpool.h index 66b08fece..ce1bc5799 100644 --- a/src/common/threadpool.h +++ b/src/common/threadpool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/unordered_containers_boost_serialization.h b/src/common/unordered_containers_boost_serialization.h index 67bde676c..bc6dd8e7a 100644 --- a/src/common/unordered_containers_boost_serialization.h +++ b/src/common/unordered_containers_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/updates.cpp b/src/common/updates.cpp index af38d7a54..654ad068c 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/updates.h b/src/common/updates.h index 0bfe26c3f..64e4eea50 100644 --- a/src/common/updates.h +++ b/src/common/updates.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/common/utf8.h b/src/common/utf8.h index 60247f1b2..434de15e7 100644 --- a/src/common/utf8.h +++ b/src/common/utf8.h @@ -1,4 +1,5 @@ -// Copyright (c) 2019, The Monero Project +// Copyright (c) 2019-2022, The Monero Project + // // All rights reserved. // diff --git a/src/common/util.cpp b/src/common/util.cpp index af9843b95..f0de73a06 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -59,7 +59,7 @@ #include "include_base_utils.h" #include "file_io_utils.h" #include "wipeable_string.h" -#include "misc_os_dependent.h" +#include "time_helper.h" using namespace epee; #include "crypto/crypto.h" @@ -85,7 +85,7 @@ using namespace epee; #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> #include <boost/format.hpp> -#include <openssl/sha.h> +#include <openssl/evp.h> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "util" @@ -941,14 +941,7 @@ std::string get_nix_version_display_string() bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash) { - SHA256_CTX ctx; - if (!SHA256_Init(&ctx)) - return false; - if (!SHA256_Update(&ctx, data, len)) - return false; - if (!SHA256_Final((unsigned char*)hash.data, &ctx)) - return false; - return true; + return EVP_Digest(data, len, (unsigned char*) hash.data, NULL, EVP_sha256(), NULL) != 0; } bool sha256sum(const std::string &filename, crypto::hash &hash) @@ -961,8 +954,8 @@ std::string get_nix_version_display_string() if (!f) return false; std::ifstream::pos_type file_size = f.tellg(); - SHA256_CTX ctx; - if (!SHA256_Init(&ctx)) + std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free); + if (!EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr)) return false; size_t size_left = file_size; f.seekg(0, std::ios::beg); @@ -973,12 +966,12 @@ std::string get_nix_version_display_string() f.read(buf, read_size); if (!f || !f.good()) return false; - if (!SHA256_Update(&ctx, buf, read_size)) + if (!EVP_DigestUpdate(ctx.get(), buf, read_size)) return false; size_left -= read_size; } f.close(); - if (!SHA256_Final((unsigned char*)hash.data, &ctx)) + if (!EVP_DigestFinal_ex(ctx.get(), (unsigned char*)hash.data, nullptr)) return false; return true; } diff --git a/src/common/util.h b/src/common/util.h index 6366e5cb4..f489594e8 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -231,7 +231,27 @@ namespace tools bool is_privacy_preserving_network(const std::string &address); int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate + /** + * \brief Creates a SHA-256 digest of a data buffer + * + * \param[in] data pointer to the buffer + * \param[in] len size of the buffer in bytes + * \param[out] hash where message digest will be written to + * + * \returns true if successful, false otherwise + */ bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash); + + /** + * \brief Creates a SHA-256 digest of a file's contents, equivalent to the sha256sum command in Linux + * + * \param[in] filename path to target file + * \param[out] hash where message digest will be written to + * + * \returns true if successful, false if the file can not be opened or there is an OpenSSL failure + * + * \throws ios_base::failure if after the file is successfully opened, an error occurs during reading + */ bool sha256sum(const std::string &filename, crypto::hash &hash); boost::optional<bool> is_hdd(const char *path); diff --git a/src/common/varint.h b/src/common/varint.h index 133622a67..9f8b9a4ab 100644 --- a/src/common/varint.h +++ b/src/common/varint.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 3f0f7d34b..595c7f966 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -58,27 +58,7 @@ include_directories(${RANDOMX_INCLUDE}) set(crypto_headers) -set(crypto_private_headers - blake256.h - chacha.h - crypto-ops.h - crypto.h - generic-ops.h - groestl.h - groestl_tables.h - hash-ops.h - hash.h - hmac-keccak.h - initializer.h - jh.h - keccak.h - oaes_config.h - oaes_lib.h - random.h - skein.h - skein_port.h - CryptonightR_JIT.h - CryptonightR_template.h) +monero_find_all_headers(crypto_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cncrypto ${crypto_private_headers}) diff --git a/src/crypto/blake256.c b/src/crypto/blake256.c index 831a90302..7e302bcad 100644 --- a/src/crypto/blake256.c +++ b/src/crypto/blake256.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/blake256.h b/src/crypto/blake256.h index 5abdb79b9..f727bddee 100644 --- a/src/crypto/blake256.h +++ b/src/crypto/blake256.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/c_threads.h b/src/crypto/c_threads.h index 5b2fcddd3..c5431cb8d 100644 --- a/src/crypto/c_threads.h +++ b/src/crypto/c_threads.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/chacha.h b/src/crypto/chacha.h index a1158be7e..74f05cbe8 100644 --- a/src/crypto/chacha.h +++ b/src/crypto/chacha.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops-data.c b/src/crypto/crypto-ops-data.c index d16fd9429..1a85de60d 100644 --- a/src/crypto/crypto-ops-data.c +++ b/src/crypto/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 508709280..4b392d472 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -3830,15 +3830,51 @@ int sc_isnonzero(const unsigned char *s) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } -int ge_p3_is_point_at_infinity(const ge_p3 *p) { - // X = 0 and Y == Z - int n; - for (n = 0; n < 10; ++n) +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { + // https://eprint.iacr.org/2008/522 + // X == T == 0 and Y/Z == 1 + // note: convert all pieces to canonical bytes in case rounding is required (i.e. an element is > q) + // note2: even though T = XY/Z is true for valid point representations (implying it isn't necessary to + // test T == 0), the input to this function might NOT be valid, so we must test T == 0 + char result_X_bytes[32]; + fe_tobytes((unsigned char*)&result_X_bytes, p->X); + + // X != 0 + for (int i = 0; i < 32; ++i) { - if (p->X[n] | p->T[n]) + if (result_X_bytes[i]) return 0; - if (p->Y[n] != p->Z[n]) + } + + char result_T_bytes[32]; + fe_tobytes((unsigned char*)&result_T_bytes, p->T); + + // T != 0 + for (int i = 0; i < 32; ++i) + { + if (result_T_bytes[i]) return 0; } - return 1; + + char result_Y_bytes[32]; + char result_Z_bytes[32]; + fe_tobytes((unsigned char*)&result_Y_bytes, p->Y); + fe_tobytes((unsigned char*)&result_Z_bytes, p->Z); + + // Y != Z + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != result_Z_bytes[i]) + return 0; + } + + // is Y nonzero? then Y/Z == 1 + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != 0) + return 1; + } + + // Y/Z = 0/0 + return 0; } diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 22f76974b..e4901e080 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -162,4 +162,4 @@ void fe_add(fe h, const fe f, const fe g); void fe_tobytes(unsigned char *, const fe); void fe_invert(fe out, const fe z); -int ge_p3_is_point_at_infinity(const ge_p3 *p); +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p); diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 0059dd7f5..77a36069a 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -749,4 +749,28 @@ POP_WARNINGS sc_sub(&h, &h, &sum); return sc_isnonzero(&h) == 0; } + + void crypto_ops::derive_view_tag(const key_derivation &derivation, size_t output_index, view_tag &view_tag) { + #pragma pack(push, 1) + struct { + char salt[8]; // view tag domain-separator + key_derivation derivation; + char output_index[(sizeof(size_t) * 8 + 6) / 7]; + } buf; + #pragma pack(pop) + + char *end = buf.output_index; + memcpy(buf.salt, "view_tag", 8); // leave off null terminator + buf.derivation = derivation; + tools::write_varint(end, output_index); + assert(end <= buf.output_index + sizeof buf.output_index); + + // view_tag_full = H[salt|derivation|output_index] + hash view_tag_full; + cn_fast_hash(&buf, end - reinterpret_cast<char *>(&buf), view_tag_full); + + // only need a slice of view_tag_full to realize optimal perf/space efficiency + static_assert(sizeof(crypto::view_tag) <= sizeof(view_tag_full), "view tag should not be larger than hash result"); + memcpy(&view_tag, &view_tag_full, sizeof(crypto::view_tag)); + } } diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 7ddc0150f..d8cd6c6a0 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -64,6 +64,11 @@ namespace crypto { friend class crypto_ops; }; + POD_CLASS public_key_memsafe : epee::mlocked<tools::scrubbed<public_key>> { + public_key_memsafe() = default; + public_key_memsafe(const public_key &original) { memcpy(this->data, original.data, 32); } + }; + using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>; POD_CLASS public_keyV { @@ -94,15 +99,19 @@ namespace crypto { ec_scalar c, r; friend class crypto_ops; }; + + POD_CLASS view_tag { + char data; + }; #pragma pack(pop) void hash_to_scalar(const void *data, size_t length, ec_scalar &res); void random32_unbiased(unsigned char *bytes); static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && - sizeof(public_key) == 32 && sizeof(secret_key) == 32 && + sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && - sizeof(signature) == 64, "Invalid structure size"); + sizeof(signature) == 64 && sizeof(view_tag) == 1, "Invalid structure size"); class crypto_ops { crypto_ops(); @@ -146,6 +155,8 @@ namespace crypto { const public_key *const *, std::size_t, const signature *); friend bool check_ring_signature(const hash &, const key_image &, const public_key *const *, std::size_t, const signature *); + static void derive_view_tag(const key_derivation &, std::size_t, view_tag &); + friend void derive_view_tag(const key_derivation &, std::size_t, view_tag &); }; void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes); @@ -292,6 +303,14 @@ namespace crypto { return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig); } + /* Derive a 1-byte view tag from the sender-receiver shared secret to reduce scanning time. + * When scanning outputs that were not sent to the user, checking the view tag for a match removes the need to proceed with expensive EC operations + * for an expected 99.6% of outputs (expected false positive rate = 1/2^8 = 1/256 = 0.4% = 100% - 99.6%). + */ + inline void derive_view_tag(const key_derivation &derivation, std::size_t output_index, view_tag &vt) { + crypto_ops::derive_view_tag(derivation, output_index, vt); + } + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; } @@ -307,12 +326,20 @@ namespace crypto { inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; } + inline std::ostream &operator <<(std::ostream &o, const crypto::view_tag &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } const extern crypto::public_key null_pkey; const extern crypto::secret_key null_skey; + + inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; } + inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; } } CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) +CRYPTO_MAKE_COMPARABLE(view_tag) diff --git a/src/crypto/crypto_ops_builder/README.md b/src/crypto/crypto_ops_builder/README.md index a3acb11e8..831c6a63c 100644 --- a/src/crypto/crypto_ops_builder/README.md +++ b/src/crypto/crypto_ops_builder/README.md @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2020, The Monero Project +Copyright (c) 2014-2022, The Monero Project ## Crypto Ops Builder diff --git a/src/crypto/crypto_ops_builder/crypto-ops-data.c b/src/crypto/crypto_ops_builder/crypto-ops-data.c index 64fd15070..4785f975f 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-data.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops-old.c b/src/crypto/crypto_ops_builder/crypto-ops-old.c index da85ee534..5d632809e 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-old.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-old.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops.h b/src/crypto/crypto_ops_builder/crypto-ops.h index d719743c4..568bf2a37 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py index 08cead175..16b6c0ba9 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py @@ -15,7 +15,7 @@ print("maybe someone smart can replace the sed with perl..") a = "" license = textwrap.dedent("""\ - // Copyright (c) 2014-2020, The Monero Project + // Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h index 613b718f5..8c0cbcda1 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/duration.h b/src/crypto/duration.h index 493874288..25d1c0b8c 100644 --- a/src/crypto/duration.h +++ b/src/crypto/duration.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/crypto/generic-ops.h b/src/crypto/generic-ops.h index 9aa7b065a..5a5e09f9b 100644 --- a/src/crypto/generic-ops.h +++ b/src/crypto/generic-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl.h b/src/crypto/groestl.h index 7483db9b6..899660cb1 100644 --- a/src/crypto/groestl.h +++ b/src/crypto/groestl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl_tables.h b/src/crypto/groestl_tables.h index 7bf03afd7..556354c47 100644 --- a/src/crypto/groestl_tables.h +++ b/src/crypto/groestl_tables.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-blake.c b/src/crypto/hash-extra-blake.c index 4cc915cdd..1557269e6 100644 --- a/src/crypto/hash-extra-blake.c +++ b/src/crypto/hash-extra-blake.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-groestl.c b/src/crypto/hash-extra-groestl.c index dec21310d..96230aed7 100644 --- a/src/crypto/hash-extra-groestl.c +++ b/src/crypto/hash-extra-groestl.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c index 0604003bd..4d7481c07 100644 --- a/src/crypto/hash-extra-jh.c +++ b/src/crypto/hash-extra-jh.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c index 55bd4ddec..9ea9c4faa 100644 --- a/src/crypto/hash-extra-skein.c +++ b/src/crypto/hash-extra-skein.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index 1cd502994..b7ec80d7c 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash.c b/src/crypto/hash.c index a917115fe..7c761a1b9 100644 --- a/src/crypto/hash.c +++ b/src/crypto/hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 4b99bebaa..2812422e0 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hmac-keccak.c b/src/crypto/hmac-keccak.c index edcb2065e..771fcc27e 100644 --- a/src/crypto/hmac-keccak.c +++ b/src/crypto/hmac-keccak.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hmac-keccak.h b/src/crypto/hmac-keccak.h index c450860d4..6b3633617 100644 --- a/src/crypto/hmac-keccak.h +++ b/src/crypto/hmac-keccak.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/initializer.h b/src/crypto/initializer.h index e9a7d97e2..90c09a087 100644 --- a/src/crypto/initializer.h +++ b/src/crypto/initializer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 72d472d8a..f098cbdf0 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -31,54 +31,83 @@ const uint64_t keccakf_rndc[24] = 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 }; -const int keccakf_rotc[24] = -{ - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 -}; - -const int keccakf_piln[24] = -{ - 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, - 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 -}; - // update the state with given number of rounds void keccakf(uint64_t st[25], int rounds) { - int i, j, round; + int round; uint64_t t, bc[5]; - for (round = 0; round < rounds; round++) { - + for (round = 0; round < rounds; ++round) { // Theta - for (i = 0; i < 5; i++) - bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; - - for (i = 0; i < 5; i++) { - t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); - for (j = 0; j < 25; j += 5) - st[j + i] ^= t; + bc[0] = st[0] ^ st[5] ^ st[10] ^ st[15] ^ st[20]; + bc[1] = st[1] ^ st[6] ^ st[11] ^ st[16] ^ st[21]; + bc[2] = st[2] ^ st[7] ^ st[12] ^ st[17] ^ st[22]; + bc[3] = st[3] ^ st[8] ^ st[13] ^ st[18] ^ st[23]; + bc[4] = st[4] ^ st[9] ^ st[14] ^ st[19] ^ st[24]; + +#define THETA(i) { \ + t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); \ + st[i ] ^= t; \ + st[i + 5] ^= t; \ + st[i + 10] ^= t; \ + st[i + 15] ^= t; \ + st[i + 20] ^= t; \ } + THETA(0); + THETA(1); + THETA(2); + THETA(3); + THETA(4); + // Rho Pi t = st[1]; - for (i = 0; i < 24; i++) { - j = keccakf_piln[i]; - bc[0] = st[j]; - st[j] = ROTL64(t, keccakf_rotc[i]); - t = bc[0]; - } + st[ 1] = ROTL64(st[ 6], 44); + st[ 6] = ROTL64(st[ 9], 20); + st[ 9] = ROTL64(st[22], 61); + st[22] = ROTL64(st[14], 39); + st[14] = ROTL64(st[20], 18); + st[20] = ROTL64(st[ 2], 62); + st[ 2] = ROTL64(st[12], 43); + st[12] = ROTL64(st[13], 25); + st[13] = ROTL64(st[19], 8); + st[19] = ROTL64(st[23], 56); + st[23] = ROTL64(st[15], 41); + st[15] = ROTL64(st[ 4], 27); + st[ 4] = ROTL64(st[24], 14); + st[24] = ROTL64(st[21], 2); + st[21] = ROTL64(st[ 8], 55); + st[ 8] = ROTL64(st[16], 45); + st[16] = ROTL64(st[ 5], 36); + st[ 5] = ROTL64(st[ 3], 28); + st[ 3] = ROTL64(st[18], 21); + st[18] = ROTL64(st[17], 15); + st[17] = ROTL64(st[11], 10); + st[11] = ROTL64(st[ 7], 6); + st[ 7] = ROTL64(st[10], 3); + st[10] = ROTL64(t, 1); // Chi - for (j = 0; j < 25; j += 5) { - for (i = 0; i < 5; i++) - bc[i] = st[j + i]; - for (i = 0; i < 5; i++) - st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; +#define CHI(j) { \ + const uint64_t st0 = st[j ]; \ + const uint64_t st1 = st[j + 1]; \ + const uint64_t st2 = st[j + 2]; \ + const uint64_t st3 = st[j + 3]; \ + const uint64_t st4 = st[j + 4]; \ + st[j ] ^= ~st1 & st2; \ + st[j + 1] ^= ~st2 & st3; \ + st[j + 2] ^= ~st3 & st4; \ + st[j + 3] ^= ~st4 & st0; \ + st[j + 4] ^= ~st0 & st1; \ } + CHI( 0); + CHI( 5); + CHI(10); + CHI(15); + CHI(20); + // Iota st[0] ^= keccakf_rndc[round]; } diff --git a/src/crypto/random.c b/src/crypto/random.c index 1e3d9beff..cfb637fb4 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/random.h b/src/crypto/random.h index 8b81e7a66..d50f29430 100644 --- a/src/crypto/random.h +++ b/src/crypto/random.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index 801987e37..40ef96ac9 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // @@ -63,6 +63,7 @@ static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static randomx_dataset *rx_dataset; static int rx_dataset_nomem; +static int rx_dataset_nolp; static uint64_t rx_dataset_height; static THREADV randomx_vm *rx_vm = NULL; @@ -316,10 +317,11 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) { rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); if(rx_vm == NULL) { //large pages failed mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); + rx_dataset_nolp = 1; } } if (rx_vm == NULL) @@ -370,5 +372,6 @@ void rx_stop_mining(void) { randomx_release_dataset(rd); } rx_dataset_nomem = 0; + rx_dataset_nolp = 0; CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } diff --git a/src/crypto/skein_port.h b/src/crypto/skein_port.h index 94fd44035..2b701e8cc 100644 --- a/src/crypto/skein_port.h +++ b/src/crypto/skein_port.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 5a773f3cf..0de7db505 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -51,6 +51,12 @@ #define INIT_SIZE_BLK 8 #define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) +#if defined(_MSC_VER) +#define THREADV __declspec(thread) +#else +#define THREADV __thread +#endif + extern void aesb_single_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); extern void aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); @@ -89,6 +95,28 @@ static inline int use_v4_jit(void) #endif } +#if defined(__x86_64__) || defined(__aarch64__) +static inline int force_software_aes(void) +{ + static int use = -1; + + if (use != -1) + return use; + + const char *env = getenv("MONERO_USE_SOFTWARE_AES"); + if (!env) { + use = 0; + } + else if (!strcmp(env, "0") || !strcmp(env, "no")) { + use = 0; + } + else { + use = 1; + } + return use; +} +#endif + #define VARIANT1_1(p) \ do if (variant == 1) \ { \ @@ -437,12 +465,6 @@ static inline int use_v4_jit(void) _b1 = _b; \ _b = _c; \ -#if defined(_MSC_VER) -#define THREADV __declspec(thread) -#else -#define THREADV __thread -#endif - #pragma pack(push, 1) union cn_slow_hash_state { @@ -498,25 +520,6 @@ STATIC INLINE void xor64(uint64_t *a, const uint64_t b) * @return true if the CPU supports AES, false otherwise */ -STATIC INLINE int force_software_aes(void) -{ - static int use = -1; - - if (use != -1) - return use; - - const char *env = getenv("MONERO_USE_SOFTWARE_AES"); - if (!env) { - use = 0; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use = 0; - } - else { - use = 1; - } - return use; -} STATIC INLINE int check_aes_hw(void) { @@ -1009,6 +1012,44 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int } #elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__)) +#ifdef __aarch64__ +#include <sys/mman.h> +THREADV uint8_t *hp_state = NULL; +THREADV int hp_malloced = 0; + +void cn_slow_hash_allocate_state(void) +{ + if(hp_state != NULL) + return; + +#ifndef MAP_HUGETLB +#define MAP_HUGETLB 0 +#endif + hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); + + if(hp_state == MAP_FAILED) + hp_state = NULL; + if(hp_state == NULL) + { + hp_malloced = 1; + hp_state = (uint8_t *) malloc(MEMORY); + } +} + +void cn_slow_hash_free_state(void) +{ + if(hp_state == NULL) + return; + + if (hp_malloced) + free(hp_state); + else + munmap(hp_state, MEMORY); + hp_state = NULL; + hp_malloced = 0; +} +#else void cn_slow_hash_allocate_state(void) { // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c @@ -1020,6 +1061,7 @@ void cn_slow_hash_free_state(void) // As above return; } +#endif #if defined(__GNUC__) #define RDATA_ALIGN16 __attribute__ ((aligned(16))) @@ -1060,6 +1102,23 @@ union cn_slow_hash_state * and moving between vector and regular registers stalls the pipeline. */ #include <arm_neon.h> +#ifndef __APPLE__ +#include <sys/auxv.h> +#include <asm/hwcap.h> +#endif + +STATIC INLINE int check_aes_hw(void) +{ +#ifdef __APPLE__ + return 1; +#else + static int supported = -1; + + if(supported < 0) + supported = (getauxval(AT_HWCAP) & HWCAP_AES) != 0; + return supported; +#endif +} #define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE) @@ -1156,7 +1215,6 @@ __asm__( STATIC INLINE void aes_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey, int nblocks) { const uint8x16_t *k = (const uint8x16_t *)expandedKey, zero = {0}; - uint8x16_t tmp; int i; for (i=0; i<nblocks; i++) @@ -1191,7 +1249,6 @@ STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, const u { const uint8x16_t *k = (const uint8x16_t *)expandedKey; const uint8x16_t *x = (const uint8x16_t *)xor; - uint8x16_t tmp; int i; for (i=0; i<nblocks; i++) @@ -1244,16 +1301,17 @@ STATIC INLINE void aligned_free(void *ptr) } #endif /* FORCE_USE_HEAP */ +STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) +{ + U64(a)[0] ^= U64(b)[0]; + U64(a)[1] ^= U64(b)[1]; +} + void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height) { RDATA_ALIGN16 uint8_t expandedKey[240]; -#ifndef FORCE_USE_HEAP - RDATA_ALIGN16 uint8_t local_hp_state[MEMORY]; -#else - uint8_t *local_hp_state = (uint8_t *)aligned_malloc(MEMORY,16); -#endif - + uint8_t *local_hp_state; uint8_t text[INIT_SIZE_BYTE]; RDATA_ALIGN16 uint64_t a[2]; RDATA_ALIGN16 uint64_t b[4]; @@ -1264,12 +1322,22 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int size_t i, j; uint64_t *p = NULL; + oaes_ctx *aes_ctx = NULL; + int useAes = !force_software_aes() && check_aes_hw(); static void (*const extra_hashes[4])(const void *, size_t, char *) = { hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein }; + // this isn't supposed to happen, but guard against it for now. + if(hp_state == NULL) + cn_slow_hash_allocate_state(); + + // locals to avoid constant TLS dereferencing + local_hp_state = hp_state; + + // locals to avoid constant TLS dereferencing /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ if (prehashed) { @@ -1287,11 +1355,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int * the 2MB large random access buffer. */ - aes_expand_key(state.hs.b, expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + if(useAes) { - aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK); - memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + aes_expand_key(state.hs.b, expandedKey); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK); + memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + } + } + else + { + aes_ctx = (oaes_ctx *) oaes_alloc(); + oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + for(j = 0; j < INIT_SIZE_BLK; j++) + aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); + + memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + } } U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; @@ -1307,13 +1390,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int _b = vld1q_u8((const uint8_t *)b); _b1 = vld1q_u8(((const uint8_t *)b) + AES_BLOCK_SIZE); - for(i = 0; i < ITER / 2; i++) + if(useAes) { - pre_aes(); - _c = vaeseq_u8(_c, zero); - _c = vaesmcq_u8(_c); - _c = veorq_u8(_c, _a); - post_aes(); + for(i = 0; i < ITER / 2; i++) + { + pre_aes(); + _c = vaeseq_u8(_c, zero); + _c = vaesmcq_u8(_c); + _c = veorq_u8(_c, _a); + post_aes(); + } + } + else + { + for(i = 0; i < ITER / 2; i++) + { + pre_aes(); + aesb_single_round((uint8_t *) &_c, (uint8_t *) &_c, (uint8_t *) &_a); + post_aes(); + } + } /* CryptoNight Step 4: Sequentially pass through the mixing buffer and use 10 rounds @@ -1322,11 +1418,27 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int memcpy(text, state.init, INIT_SIZE_BYTE); - aes_expand_key(&state.hs.b[32], expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + if(useAes) + { + aes_expand_key(&state.hs.b[32], expandedKey); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + // add the xor to the pseudo round + aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); + } + } + else { - // add the xor to the pseudo round - aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); + oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + for(j = 0; j < INIT_SIZE_BLK; j++) + { + xor_blocks(&text[j * AES_BLOCK_SIZE], &local_hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); + aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); + } + } + oaes_free((OAES_CTX **) &aes_ctx); } /* CryptoNight Step 5: Apply Keccak to the state again, and then @@ -1339,10 +1451,6 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int memcpy(state.init, text, INIT_SIZE_BYTE); hash_permutation(&state.hs); extra_hashes[state.hs.b[0] & 3](&state, 200, hash); - -#ifdef FORCE_USE_HEAP - aligned_free(local_hp_state); -#endif } #else /* aarch64 && crypto */ diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 8f3ea3339..93a1bce4d 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/crypto/wallet/CMakeLists.txt b/src/crypto/wallet/CMakeLists.txt index 4ed986dce..ac1bdf7fd 100644 --- a/src/crypto/wallet/CMakeLists.txt +++ b/src/crypto/wallet/CMakeLists.txt @@ -1,4 +1,5 @@ -# Copyright (c) 2020, The Monero Project +# Copyright (c) 2020-2022, The Monero Project + # # All rights reserved. # diff --git a/src/crypto/wallet/crypto.h b/src/crypto/wallet/crypto.h index a4c5d5a07..cee0ca18e 100644 --- a/src/crypto/wallet/crypto.h +++ b/src/crypto/wallet/crypto.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/crypto/wallet/empty.h.in b/src/crypto/wallet/empty.h.in index ac252e1bd..b884a57b5 100644 --- a/src/crypto/wallet/empty.h.in +++ b/src/crypto/wallet/empty.h.in @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index e386ec4ea..1414be1b2 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -56,20 +56,7 @@ set(cryptonote_basic_sources set(cryptonote_basic_headers) -set(cryptonote_basic_private_headers - account.h - account_boost_serialization.h - connection_context.h - cryptonote_basic.h - cryptonote_basic_impl.h - cryptonote_boost_serialization.h - cryptonote_format_utils.h - difficulty.h - hardfork.h - merge_mining.h - miner.h - tx_extra.h - verification_context.h) +monero_find_all_headers(cryptonote_basic_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cryptonote_basic ${cryptonote_basic_private_headers}) diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 36ff41684..2ac455fda 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -253,11 +253,6 @@ DISABLE_VS_WARNINGS(4244 4345) return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); } //----------------------------------------------------------------- - void account_base::finalize_multisig(const crypto::public_key &spend_public_key) - { - m_keys.m_account_address.m_spend_public_key = spend_public_key; - } - //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 5288b9b04..2ee9545d4 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -55,8 +55,6 @@ namespace cryptonote KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) END_KV_SERIALIZE_MAP() - account_keys& operator=(account_keys const&) = default; - void encrypt(const crypto::chacha_key &key); void decrypt(const crypto::chacha_key &key); void encrypt_viewkey(const crypto::chacha_key &key); @@ -82,7 +80,6 @@ namespace cryptonote void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys); - void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(network_type nettype) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const; diff --git a/src/cryptonote_basic/account_boost_serialization.h b/src/cryptonote_basic/account_boost_serialization.h index d97a5a854..ce0e4501a 100644 --- a/src/cryptonote_basic/account_boost_serialization.h +++ b/src/cryptonote_basic/account_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/blobdatatype.h b/src/cryptonote_basic/blobdatatype.h index 7f899993b..49735c034 100644 --- a/src/cryptonote_basic/blobdatatype.h +++ b/src/cryptonote_basic/blobdatatype.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/connection_context.cpp b/src/cryptonote_basic/connection_context.cpp index a0b8ca1f1..4395bad9f 100644 --- a/src/cryptonote_basic/connection_context.cpp +++ b/src/cryptonote_basic/connection_context.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index ee26a0c07..818999a60 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +// Parts of this file are originally copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net #pragma once #include <unordered_set> @@ -34,7 +35,6 @@ #include <algorithm> #include <boost/date_time/posix_time/posix_time.hpp> #include "net/net_utils_base.h" -#include "copyable_atomic.h" #include "crypto/hash.h" namespace cryptonote @@ -55,6 +55,37 @@ namespace cryptonote state_normal }; + /* + This class was originally from the EPEE module. It is identical in function to std::atomic<uint32_t> except + that it has copy-construction and copy-assignment defined, which means that earliers devs didn't have to write + custom copy-contructors and copy-assingment operators for the outer class, cryptonote_connection_context. + cryptonote_connection_context should probably be refactored because it is both trying to be POD-like while + also (very loosely) controlling access to its atomic members. + */ + class copyable_atomic: public std::atomic<uint32_t> + { + public: + copyable_atomic() + {}; + copyable_atomic(uint32_t value) + { store(value); } + copyable_atomic(const copyable_atomic& a):std::atomic<uint32_t>(a.load()) + {} + copyable_atomic& operator= (const copyable_atomic& a) + { + store(a.load()); + return *this; + } + uint32_t operator++() + { + return std::atomic<uint32_t>::operator++(); + } + uint32_t operator++(int fake) + { + return std::atomic<uint32_t>::operator++(fake); + } + }; + static constexpr int handshake_command() noexcept { return 1001; } bool handshake_complete() const noexcept { return m_state != state_before_handshake; } @@ -67,7 +98,7 @@ namespace cryptonote uint64_t m_remote_blockchain_height; uint64_t m_last_response_height; boost::posix_time::ptime m_last_request_time; - epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise + copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise crypto::hash m_last_known_hash; uint32_t m_pruning_seed; uint16_t m_rpc_port; @@ -77,8 +108,8 @@ namespace cryptonote int m_expect_response; uint64_t m_expect_height; size_t m_num_requested; - epee::copyable_atomic m_new_stripe_notification{0}; - epee::copyable_atomic m_idle_peer_notification{0}; + copyable_atomic m_new_stripe_notification{0}; + copyable_atomic m_idle_peer_notification{0}; }; inline std::string get_protocol_state_string(cryptonote_connection_context::state s) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index d111f6f32..127958796 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -74,6 +74,7 @@ namespace cryptonote crypto::hash hash; }; + // outputs <= HF_VERSION_VIEW_TAGS struct txout_to_key { txout_to_key() { } @@ -81,6 +82,19 @@ namespace cryptonote crypto::public_key key; }; + // outputs >= HF_VERSION_VIEW_TAGS + struct txout_to_tagged_key + { + txout_to_tagged_key() { } + 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 + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + FIELD(view_tag) + END_SERIALIZE() + }; /* inputs */ @@ -137,7 +151,7 @@ namespace cryptonote typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> txin_v; - typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key> txout_target_v; + typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key, txout_to_tagged_key> txout_target_v; //typedef std::pair<uint64_t, txout> out_t; struct tx_out @@ -562,6 +576,7 @@ VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0); VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc); VARIANT_TAG(binary_archive, cryptonote::block, 0xbb); @@ -572,6 +587,7 @@ VARIANT_TAG(json_archive, cryptonote::txin_to_key, "key"); VARIANT_TAG(json_archive, cryptonote::txout_to_script, "script"); VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(json_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(json_archive, cryptonote::transaction, "tx"); VARIANT_TAG(json_archive, cryptonote::block, "block"); @@ -582,5 +598,6 @@ VARIANT_TAG(debug_archive, cryptonote::txin_to_key, "key"); VARIANT_TAG(debug_archive, cryptonote::txout_to_script, "script"); VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(debug_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(debug_archive, cryptonote::transaction, "tx"); VARIANT_TAG(debug_archive, cryptonote::block, "block"); diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 2600854a9..c60a62689 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 1303670d2..b423573c5 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index c6b81b094..493c9d91d 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -71,7 +71,11 @@ namespace boost { a & reinterpret_cast<char (&)[sizeof(crypto::key_image)]>(x); } - + template <class Archive> + inline void serialize(Archive &a, crypto::view_tag &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::view_tag)]>(x); + } template <class Archive> inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver) { @@ -103,6 +107,13 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, cryptonote::txout_to_tagged_key &x, const boost::serialization::version_type ver) + { + a & x.key; + a & x.view_tag; + } + + template <class Archive> inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver) { a & x.hash; @@ -228,6 +239,20 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, rct::BulletproofPlus &x, const boost::serialization::version_type ver) + { + a & x.V; + a & x.A; + a & x.A1; + a & x.B; + a & x.r1; + a & x.s1; + a & x.d1; + a & x.L; + a & x.R; + } + + template <class Archive> inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { a & x.s0; @@ -305,7 +330,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -321,7 +346,11 @@ namespace boost { a & x.rangeSigs; if (x.rangeSigs.empty()) + { a & x.bulletproofs; + if (ver >= 2u) + a & x.bulletproofs_plus; + } a & x.MGs; if (ver >= 1u) a & x.CLSAGs; @@ -335,7 +364,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -347,11 +376,15 @@ namespace boost //-------------- a & x.p.rangeSigs; if (x.p.rangeSigs.empty()) + { a & x.p.bulletproofs; + if (ver >= 2u) + a & x.p.bulletproofs_plus; + } a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG) + if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus) a & x.p.pseudoOuts; } @@ -392,6 +425,6 @@ namespace boost } } -BOOST_CLASS_VERSION(rct::rctSigPrunable, 1) -BOOST_CLASS_VERSION(rct::rctSig, 1) +BOOST_CLASS_VERSION(rct::rctSigPrunable, 2) +BOOST_CLASS_VERSION(rct::rctSig, 2) BOOST_CLASS_VERSION(rct::multisig_out, 1) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 17adcdc35..388013f96 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -105,7 +105,9 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { - const uint64_t bp_base = 368; + const rct::rctSig &rv = tx.rct_signatures; + const bool plus = rv.type == rct::RCTTypeBulletproofPlus; + const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) return 0; @@ -113,7 +115,7 @@ namespace cryptonote while ((1u << nlr) < n_padded_outputs) ++nlr; nlr += 6; - const size_t bp_size = 32 * (9 + 2 * nlr); + const size_t bp_size = 32 * ((plus ? 6 : 9) + 2 * nlr); CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); @@ -153,18 +155,44 @@ namespace cryptonote } for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n) { - if (tx.vout[n].target.type() != typeid(txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[n], output_public_key)) { - LOG_PRINT_L1("Unsupported output type in tx " << get_transaction_hash(tx)); + LOG_PRINT_L1("Failed to get output public key for output " << n << " in tx " << get_transaction_hash(tx)); return false; } - rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); + rv.outPk[n].dest = rct::pk2rct(output_public_key); } if (!base_only) { const bool bulletproof = rct::is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (bulletproof_plus) + { + if (rv.p.bulletproofs_plus.size() != 1) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus size in tx " << get_transaction_hash(tx)); + return false; + } + if (rv.p.bulletproofs_plus[0].L.size() < 6) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); + return false; + } + const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]); + if (max_outputs < tx.vout.size()) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus max outputs in tx " << get_transaction_hash(tx)); + return false; + } + const size_t n_amounts = tx.vout.size(); + CHECK_AND_ASSERT_MES(n_amounts == rv.outPk.size(), false, "Internal error filling out V"); + rv.p.bulletproofs_plus[0].V.resize(n_amounts); + for (size_t i = 0; i < n_amounts; ++i) + rv.p.bulletproofs_plus[0].V[i] = rct::scalarmultKey(rv.outPk[i].mask, rct::INV_EIGHT); + } + else if (bulletproof) { if (rv.p.bulletproofs.size() != 1) { @@ -306,7 +334,26 @@ namespace cryptonote { // derive secret key with subaddress - step 1: original CN derivation crypto::secret_key scalar_step1; - hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b + crypto::secret_key spend_skey = crypto::null_skey; + + if (ack.m_multisig_keys.empty()) + { + // if not multisig, use normal spend skey + spend_skey = ack.m_spend_secret_key; + } + else + { + // if multisig, use sum of multisig privkeys (local account's share of aggregate spend key) + for (const auto &multisig_key : ack.m_multisig_keys) + { + sc_add((unsigned char*)spend_skey.data, + (const unsigned char*)multisig_key.data, + (const unsigned char*)spend_skey.data); + } + } + + // computes Hs(a*R || idx) + b + hwdev.derive_secret_key(recv_derivation, real_output_index, spend_skey, scalar_step1); // step 2: add Hs(a || index_major || index_minor) crypto::secret_key subaddr_sk; @@ -400,9 +447,11 @@ namespace cryptonote if (tx.version < 2) return blob_size; const rct::rctSig &rv = tx.rct_signatures; - if (!rct::is_rct_bulletproof(rv.type)) + const bool bulletproof = rct::is_rct_bulletproof(rv.type); + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (!bulletproof && !bulletproof_plus) return blob_size; - const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs); + const size_t n_padded_outputs = bulletproof_plus ? rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus) : rct::n_bulletproof_max_amounts(rv.p.bulletproofs); uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - blob_size, "Weight overflow"); return blob_size + bp_clawback; @@ -412,8 +461,8 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); - CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG, - std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); + CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus, + std::numeric_limits<uint64_t>::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin"); @@ -431,12 +480,12 @@ namespace cryptonote while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) ++nrl; nrl += 6; - extra = 32 * (9 + 2 * nrl) + 2; + extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; weight += extra; // calculate deterministic CLSAG/MLSAG data size const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); - if (tx.rct_signatures.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(tx.rct_signatures.type)) extra = tx.vin.size() * (ring_size + 2) * 32; else extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); @@ -804,16 +853,16 @@ namespace cryptonote { for(const tx_out& out: tx.vout) { - CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: " - << out.target.type().name() << ", expected " << typeid(txout_to_key).name() - << ", in transaction id=" << get_transaction_hash(tx)); + crypto::public_key output_public_key; + CHECK_AND_ASSERT_MES(get_output_public_key(out, output_public_key), false, "Failed to get output public key (output type: " + << out.target.type().name() << "), in transaction id=" << get_transaction_hash(tx)); if (tx.version == 1) { CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output in transaction id=" << get_transaction_hash(tx)); } - if(!check_key(boost::get<txout_to_key>(out.target).key)) + if(!check_key(output_public_key)) return false; } return true; @@ -857,6 +906,30 @@ namespace cryptonote return outputs_amount; } //--------------------------------------------------------------- + bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key) + { + // before HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_key + // after HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_tagged_key + if (out.target.type() == typeid(txout_to_key)) + output_public_key = boost::get< txout_to_key >(out.target).key; + else if (out.target.type() == typeid(txout_to_tagged_key)) + output_public_key = boost::get< txout_to_tagged_key >(out.target).key; + else + { + LOG_ERROR("Unexpected output target type found: " << out.target.type().name()); + return false; + } + + return true; + } + //--------------------------------------------------------------- + boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out) + { + return out.target.type() == typeid(txout_to_tagged_key) + ? boost::optional<crypto::view_tag>(boost::get< txout_to_tagged_key >(out.target).view_tag) + : boost::optional<crypto::view_tag>(); + } + //--------------------------------------------------------------- std::string short_hash_str(const crypto::hash& h) { std::string res = string_tools::pod_to_hex(h); @@ -866,45 +939,126 @@ namespace cryptonote return res; } //--------------------------------------------------------------- - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index) + void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out) + { + out.amount = amount; + if (use_view_tags) + { + txout_to_tagged_key ttk; + ttk.key = output_public_key; + ttk.view_tag = view_tag; + out.target = ttk; + } + else + { + txout_to_key tk; + tk.key = output_public_key; + out.target = tk; + } + } + //--------------------------------------------------------------- + bool check_output_types(const transaction& tx, const uint8_t hf_version) + { + for (const auto &o: tx.vout) + { + if (hf_version > HF_VERSION_VIEW_TAGS) + { + // from v15, require outputs have view tags + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_tagged_key in transaction id=" << get_transaction_hash(tx)); + } + else if (hf_version < HF_VERSION_VIEW_TAGS) + { + // require outputs to be of type txout_to_key + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_key in transaction id=" << get_transaction_hash(tx)); + } + else //(hf_version == HF_VERSION_VIEW_TAGS) + { + // require outputs be of type txout_to_key OR txout_to_tagged_key + // to allow grace period before requiring all to be txout_to_tagged_key + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key) || o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_key or txout_to_tagged_key in transaction id=" << get_transaction_hash(tx)); + + // require all outputs in a tx be of the same type + CHECK_AND_ASSERT_MES(o.target.type() == tx.vout[0].target.type(), false, "non-matching variant types: " + << o.target.type().name() << " and " << tx.vout[0].target.type().name() << ", " + << "expected matching variant types in transaction id=" << get_transaction_hash(tx)); + } + } + return true; + } + //--------------------------------------------------------------- + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index) + { + // If there is no view tag to check, the output can possibly belong to the account. + // Will need to derive the output pub key to be certain whether or not the output belongs to the account. + if (!view_tag_opt) + return true; + + crypto::view_tag view_tag = *view_tag_opt; + + // If the output's view tag does *not* match the derived view tag, the output should not belong to the account. + // Therefore can fail out early to avoid expensive crypto ops needlessly deriving output public key to + // determine if output belongs to the account. + crypto::view_tag derived_view_tag; + crypto::derive_view_tag(derivation, output_index, derived_view_tag); + return view_tag == derived_view_tag; + } + //--------------------------------------------------------------- + bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt) { crypto::key_derivation derivation; bool r = acc.get_device().generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); crypto::public_key pk; - r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); - CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); - if (pk == out_key.key) - return true; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); + if (pk == output_public_key) + return true; + } + // try additional tx pubkeys if available if (!additional_tx_pub_keys.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); r = acc.get_device().generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); - r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); - CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); - return pk == out_key.key; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); + return pk == output_public_key; + } } return false; } //--------------------------------------------------------------- - boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev) + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt) { // try the shared tx pubkey crypto::public_key subaddress_spendkey; - hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); - auto found = subaddresses.find(subaddress_spendkey); - if (found != subaddresses.end()) - return subaddress_receive_info{ found->second, derivation }; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, derivation }; + } + // try additional tx pubkeys if available if (!additional_derivations.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); - hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); - found = subaddresses.find(subaddress_spendkey); - if (found != subaddresses.end()) - return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) + { + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + } } return boost::none; } @@ -925,8 +1079,9 @@ namespace cryptonote size_t i = 0; for(const tx_out& o: tx.vout) { - CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); - if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i)) + crypto::public_key output_public_key; + CHECK_AND_ASSERT_MES(get_output_public_key(o, output_public_key), false, "unable to get output public key from transaction out" ); + if(is_out_to_acc(acc, output_public_key, tx_pub_key, additional_tx_pub_keys, i, get_output_view_tag(o))) { outs.push_back(i); money_transfered += o.amount; @@ -1016,6 +1171,69 @@ namespace cryptonote return s; } //--------------------------------------------------------------- + uint64_t round_money_up(uint64_t amount, unsigned significant_digits) + { + // round monetary amount up with the requested amount of significant digits + + CHECK_AND_ASSERT_THROW_MES(significant_digits > 0, "significant_digits must not be 0"); + static_assert(sizeof(unsigned long long) == sizeof(uint64_t), "Unexpected unsigned long long size"); + + // we don't need speed, so we do it via text, as it's easier to get right + char buf[32]; + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)amount); + const size_t len = strlen(buf); + if (len > significant_digits) + { + bool bump = false; + char *ptr; + for (ptr = buf + significant_digits; *ptr; ++ptr) + { + // bump digits by one if the following digits past significant digits were to be 5 or more + if (*ptr != '0') + { + bump = true; + *ptr = '0'; + } + } + ptr = buf + significant_digits; + while (bump && ptr > buf) + { + --ptr; + // bumping a nine overflows + if (*ptr == '9') + *ptr = '0'; + else + { + // bumping another digit means we can stop bumping (no carry) + ++*ptr; + bump = false; + } + } + if (bump) + { + // carry reached the highest digit, we need to add a 1 in front + size_t offset = strlen(buf); + for (size_t i = offset + 1; i > 0; --i) + buf[i] = buf[i - 1]; + buf[0] = '1'; + } + } + char *end = NULL; + errno = 0; + const unsigned long long ull = strtoull(buf, &end, 10); + CHECK_AND_ASSERT_THROW_MES(ull != ULONG_MAX || errno == 0, "Failed to parse rounded amount: " << buf); + CHECK_AND_ASSERT_THROW_MES(ull != 0 || amount == 0, "Overflow in rounding"); + return ull; + } + //--------------------------------------------------------------- + std::string round_money_up(const std::string &s, unsigned significant_digits) + { + uint64_t amount; + CHECK_AND_ASSERT_THROW_MES(parse_amount(amount, s), "Failed to parse amount: " << s); + amount = round_money_up(amount, significant_digits); + return print_money(amount); + } + //--------------------------------------------------------------- crypto::hash get_blob_hash(const blobdata& blob) { crypto::hash h = null_hash; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 3fe4c44e7..8f5459ca7 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -89,13 +89,16 @@ namespace cryptonote void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index); + void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out); + bool check_output_types(const transaction& tx, const uint8_t hf_version); + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index); + bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>()); struct subaddress_receive_info { subaddress_index index; crypto::key_derivation derivation; }; - boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev); + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>()); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); @@ -126,6 +129,8 @@ namespace cryptonote bool parse_and_validate_block_from_blob(const blobdata_ref& b_blob, block& b, crypto::hash &block_hash); bool get_inputs_money_amount(const transaction& tx, uint64_t& money); uint64_t get_outs_money_amount(const transaction& tx); + bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key); + boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out); bool check_inputs_types_supported(const transaction& tx); bool check_outs_valid(const transaction& tx); bool parse_amount(uint64_t& amount, const std::string& str_amount); @@ -144,6 +149,8 @@ namespace cryptonote std::string get_unit(unsigned int decimal_point = -1); std::string print_money(uint64_t amount, unsigned int decimal_point = -1); std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point = -1); + uint64_t round_money_up(uint64_t amount, unsigned significant_digits); + std::string round_money_up(const std::string &amount, unsigned significant_digits); //--------------------------------------------------------------- template<class t_object> bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) diff --git a/src/cryptonote_basic/cryptonote_format_utils_basic.cpp b/src/cryptonote_basic/cryptonote_format_utils_basic.cpp index 29130ce46..7431a1beb 100644 --- a/src/cryptonote_basic/cryptonote_format_utils_basic.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils_basic.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index dbc2e534a..165c1936e 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index 7f5ea4597..ee9378eb9 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/events.h b/src/cryptonote_basic/events.h index 3417ece8c..18ea99800 100644 --- a/src/cryptonote_basic/events.h +++ b/src/cryptonote_basic/events.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/fwd.h b/src/cryptonote_basic/fwd.h index 901ad151b..54f3c0184 100644 --- a/src/cryptonote_basic/fwd.h +++ b/src/cryptonote_basic/fwd.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 7a5161bc8..a0ea3633f 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h index 5800c31b5..32f669c71 100644 --- a/src/cryptonote_basic/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/merge_mining.cpp b/src/cryptonote_basic/merge_mining.cpp index fcc74859f..b8d16aade 100644 --- a/src/cryptonote_basic/merge_mining.cpp +++ b/src/cryptonote_basic/merge_mining.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/merge_mining.h b/src/cryptonote_basic/merge_mining.h index 378438f7c..8fe282a76 100644 --- a/src/cryptonote_basic/merge_mining.h +++ b/src/cryptonote_basic/merge_mining.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index ae514aac6..5b0db9518 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -30,7 +30,6 @@ #include <sstream> #include <numeric> -#include <boost/interprocess/detail/atomic.hpp> #include <boost/algorithm/string.hpp> #include "misc_language.h" #include "syncobj.h" @@ -271,13 +270,13 @@ namespace cryptonote // restart all threads { CRITICAL_REGION_LOCAL(m_threads_lock); - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); + m_stop = true; while (m_threads_active > 0) misc_utils::sleep_no_w(100); m_threads.clear(); } - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); - boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); + m_stop = false; + m_thread_index = 0; for(size_t i = 0; i != m_threads_total; i++) m_threads.push_back(boost::thread(m_attrs, boost::bind(&miner::worker_thread, this))); } @@ -394,8 +393,8 @@ namespace cryptonote request_block_template();//lets update block template - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); - boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); + m_stop = false; + m_thread_index = 0; set_is_background_mining_enabled(do_background); set_ignore_battery(ignore_battery); @@ -435,7 +434,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- void miner::send_stop_signal() { - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); + m_stop = true; } extern "C" void rx_stop_mining(void); //----------------------------------------------------------------------------------------------------- @@ -524,7 +523,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- bool miner::worker_thread() { - uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); + const uint32_t th_local_index = m_thread_index++; // atomically increment, getting value before increment MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]"); MGINFO("Miner thread was started ["<< th_local_index << "]"); uint32_t nonce = m_starter_nonce + th_local_index; diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index df3f56f68..72dc12762 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -118,14 +118,14 @@ namespace cryptonote }; - volatile uint32_t m_stop; + std::atomic<bool> m_stop; epee::critical_section m_template_lock; block m_template; std::atomic<uint32_t> m_template_no; std::atomic<uint32_t> m_starter_nonce; difficulty_type m_diffic; uint64_t m_height; - volatile uint32_t m_thread_index; + std::atomic<uint32_t> m_thread_index; volatile uint32_t m_threads_total; std::atomic<uint32_t> m_threads_active; std::atomic<int32_t> m_pausers_count; diff --git a/src/cryptonote_basic/subaddress_index.h b/src/cryptonote_basic/subaddress_index.h index 3f5f120d9..612d1e8a5 100644 --- a/src/cryptonote_basic/subaddress_index.h +++ b/src/cryptonote_basic/subaddress_index.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 76efc22d3..141f72352 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 2535cba95..34157218f 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 915835d1b..962346017 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -126,6 +126,7 @@ #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT 1000 #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT 20000 +#define MAX_RPC_CONTENT_LENGTH 1048576 // 1 MB #define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000 #define P2P_LOCAL_GRAY_PEERLIST_LIMIT 5000 @@ -169,6 +170,7 @@ #define HF_VERSION_MIN_MIXIN_4 6 #define HF_VERSION_MIN_MIXIN_6 7 #define HF_VERSION_MIN_MIXIN_10 8 +#define HF_VERSION_MIN_MIXIN_15 15 #define HF_VERSION_ENFORCE_RCT 6 #define HF_VERSION_PER_BYTE_FEE 8 #define HF_VERSION_SMALLER_BP 10 @@ -182,14 +184,19 @@ #define HF_VERSION_EXACT_COINBASE 13 #define HF_VERSION_CLSAG 13 #define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13 +#define HF_VERSION_BULLETPROOF_PLUS 15 +#define HF_VERSION_VIEW_TAGS 15 +#define HF_VERSION_2021_SCALING 15 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 +#define CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES 2 #define HASH_OF_HASHES_STEP 512 #define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes #define BULLETPROOF_MAX_OUTPUTS 16 +#define BULLETPROOF_PLUS_MAX_OUTPUTS 16 #define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase #define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved @@ -221,6 +228,8 @@ namespace config // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; + const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus"; + const char HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT[] = "bulletproof_plus_transcript"; const char HASH_KEY_RINGDB[] = "ringdsb"; const char HASH_KEY_SUBADDRESS[] = "SubAddr"; const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; @@ -229,6 +238,8 @@ namespace config const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg"; + const unsigned char HASH_KEY_CLSAG_ROUND_MULTISIG[] = "CLSAG_round_ms_merge_factor"; const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2"; const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round"; const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; @@ -236,6 +247,9 @@ namespace config const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; const unsigned char HASH_KEY_MM_SLOT = 'm'; + // Multisig + const uint32_t MULTISIG_MAX_SIGNERS{16}; + namespace testnet { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 0be8b544e..69411e379 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -35,13 +35,7 @@ set(cryptonote_core_sources set(cryptonote_core_headers) -set(cryptonote_core_private_headers - blockchain_storage_boost_serialization.h - blockchain.h - cryptonote_core.h - tx_pool.h - tx_sanity_check.h - cryptonote_tx_utils.h) +monero_find_all_headers(cryptonote_core_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cryptonote_core ${cryptonote_core_private_headers}) @@ -55,7 +49,6 @@ target_link_libraries(cryptonote_core common cncrypto blockchain_db - multisig ringct device hardforks diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3d0e81af1..5b7b4353d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -588,6 +588,7 @@ block Blockchain::pop_block_from_blockchain() CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block"); + const uint8_t previous_hf_version = get_current_hard_fork_version(); try { m_db->pop_block(popped_block, popped_txs); @@ -650,6 +651,13 @@ block Blockchain::pop_block_from_blockchain() m_tx_pool.on_blockchain_dec(top_block_height, top_block_hash); invalidate_block_template_cache(); + const uint8_t new_hf_version = get_current_hard_fork_version(); + if (new_hf_version != previous_hf_version) + { + MINFO("Validating txpool for v" << (unsigned)new_hf_version); + m_tx_pool.validate(new_hf_version); + } + return popped_block; } //------------------------------------------------------------------ @@ -1333,6 +1341,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: // one input, of type txin_gen, with height set to the block's height // correct miner tx unlock time // a non-overflowing tx amount (dubious necessity on this check) +// valid output types bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1355,15 +1364,14 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); //check outs overflow - //NOTE: not entirely sure this is necessary, given that this function is - // designed simply to make sure the total amount for a transaction - // does not overflow a uint64_t, and this transaction *is* a uint64_t... if(!check_outs_overflow(b.miner_tx)) { MERROR("miner transaction has money overflow in block " << get_block_hash(b)); return false; } + CHECK_AND_ASSERT_MES(check_output_types(b.miner_tx, hf_version), false, "miner transaction has invalid output type(s) in block " << get_block_hash(b)); + return true; } //------------------------------------------------------------------ @@ -3039,12 +3047,14 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // from v4, forbid invalid pubkeys if (hf_version >= 4) { for (const auto &o: tx.vout) { - if (o.target.type() == typeid(txout_to_key)) { - const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target); - if (!crypto::check_key(out_to_key.key)) { - tvc.m_invalid_output = true; - return false; - } + crypto::public_key output_public_key; + if (!get_output_public_key(o, output_public_key)) { + tvc.m_invalid_output = true; + return false; + } + if (!crypto::check_key(output_public_key)) { + tvc.m_invalid_output = true; + return false; } } } @@ -3135,6 +3145,39 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v15, allow bulletproofs plus + if (hf_version < HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); + if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) + { + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + + // from v16, forbid bulletproofs + if (hf_version > HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); + if (bulletproof) + { + MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + + // from v15, require view tags on outputs + if (!check_output_types(tx, hf_version)) + { + tvc.m_invalid_output = true; + return false; + } + return true; } //------------------------------------------------------------------ @@ -3175,7 +3218,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); @@ -3216,7 +3259,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { if (!tx.pruned) { @@ -3278,7 +3321,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, size_t n_unmixable = 0, n_mixable = 0; size_t min_actual_mixin = std::numeric_limits<size_t>::max(); size_t max_actual_mixin = 0; - const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_10 ? 10 : hf_version >= HF_VERSION_MIN_MIXIN_6 ? 6 : hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; + const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_15 ? 15 : hf_version >= HF_VERSION_MIN_MIXIN_10 ? 10 : hf_version >= HF_VERSION_MIN_MIXIN_6 ? 6 : hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; for (const auto& txin : tx.vin) { // non txin_to_key inputs will be rejected below @@ -3321,14 +3364,11 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - if (((hf_version == HF_VERSION_MIN_MIXIN_10 || hf_version == HF_VERSION_MIN_MIXIN_10+1) && min_actual_mixin != 10) || (hf_version >= HF_VERSION_MIN_MIXIN_10+2 && min_actual_mixin > 10)) - { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has invalid ring size (" << (min_actual_mixin + 1) << "), it should be 11"); - tvc.m_low_mixin = true; - return false; - } - - if (min_actual_mixin < min_mixin) + // The only circumstance where ring sizes less than expected are + // allowed is when spending unmixable non-RCT outputs in the chain. + // Caveat: at HF_VERSION_MIN_MIXIN_15, temporarily allow ring sizes + // of 11 to allow a grace period in the transition to larger ring size. + if (min_actual_mixin < min_mixin && !(hf_version == HF_VERSION_MIN_MIXIN_15 && min_actual_mixin == 10)) { if (n_unmixable == 0) { @@ -3342,6 +3382,15 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, tvc.m_low_mixin = true; return false; } + } else if ((hf_version > HF_VERSION_MIN_MIXIN_15 && min_actual_mixin > 15) + || (hf_version == HF_VERSION_MIN_MIXIN_15 && min_actual_mixin != 15 && min_actual_mixin != 10) // grace period to allow either 15 or 10 + || (hf_version < HF_VERSION_MIN_MIXIN_15 && hf_version >= HF_VERSION_MIN_MIXIN_10+2 && min_actual_mixin > 10) + || ((hf_version == HF_VERSION_MIN_MIXIN_10 || hf_version == HF_VERSION_MIN_MIXIN_10+1) && min_actual_mixin != 10) + ) + { + MERROR_VER("Tx " << get_transaction_hash(tx) << " has invalid ring size (" << (min_actual_mixin + 1) << "), it should be " << (min_mixin + 1)); + tvc.m_low_mixin = true; + return false; } // min/max tx version based on HF, and we accept v1 txes if having a non mixable @@ -3508,6 +3557,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: { // check all this, either reconstructed (so should really pass), or not { @@ -3543,7 +3593,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); if (n_sigs != tx.vin.size()) { MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); @@ -3552,7 +3602,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, for (size_t n = 0; n < tx.vin.size(); ++n) { bool error; - if (rv.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(rv.type)) error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); else error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); @@ -3678,11 +3728,24 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b if (version >= HF_VERSION_PER_BYTE_FEE) { lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); - div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); - assert(hi == 0); - lo /= 5; - return lo; + if (version >= HF_VERSION_2021_SCALING) + { + // min_fee_per_byte = round_up( 0.95 * block_reward * ref_weight / (fee_median^2) ) + // note: since hardfork HF_VERSION_2021_SCALING, fee_median (a.k.a. median_block_weight) equals effective long term median + div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); + assert(hi == 0); + lo -= lo / 20; + return lo; + } + else + { + // min_fee_per_byte = 0.2 * block_reward * ref_weight / (min_penalty_free_zone * fee_median) + div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); + assert(hi == 0); + lo /= 5; + return lo; + } } const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; @@ -3755,6 +3818,81 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const } //------------------------------------------------------------------ +void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const +{ + // variable names and calculations as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf + // from (earlier than) this fork, the base fee is per byte + const uint64_t Mfw = std::min(Mnw, Mlw); + + // 3 kB divided by something ? It's going to be either 0 or *very* quantized, so fold it into integer steps below + //const uint64_t Brlw = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / Mfw; + + // constant.... equal to 0, unless floating point, so fold it into integer steps below + //const uint64_t Br = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 + + //const uint64_t Fl = base_reward * Brlw / Mfw; fold Brlw from above + const uint64_t Fl = base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw); + + // fold Fl into this for better precision (and to match the test cases in the PDF) + // const uint64_t Fn = 4 * Fl; + const uint64_t Fn = 4 * base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw); + + // const uint64_t Fm = 16 * base_reward * Br / Mfw; fold Br from above + const uint64_t Fm = 16 * base_reward * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 * Mfw); + + // const uint64_t Fp = 2 * base_reward / Mnw; + + // fold Br from above, move 4Fm in the max to decrease quantization effect + //const uint64_t Fh = 4 * Fm * std::max<uint64_t>(1, Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5)); + const uint64_t Fh = std::max<uint64_t>(4 * Fm, 4 * Fm * Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5)); + + fees.resize(4); + fees[0] = cryptonote::round_money_up(Fl, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[1] = cryptonote::round_money_up(Fn, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[2] = cryptonote::round_money_up(Fm, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[3] = cryptonote::round_money_up(Fh, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); +} + +void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const +{ + const uint8_t version = get_current_hard_fork_version(); + const uint64_t db_height = m_db->height(); + + // we want Mlw = median of max((min(Mbw, 1.7 * Ml), Zm), Ml / 1.7) + // Mbw: block weight for the last 99990 blocks, 0 for the next 10 + // Ml: penalty free zone (dynamic), aka long_term_median, aka median of max((min(Mb, 1.7 * Ml), Zm), Ml / 1.7) + // Zm: 300000 (minimum penalty free zone) + // + // So we copy the current rolling median state, add 10 (grace_blocks) zeroes to it, and get back Mlw + + epee::misc_utils::rolling_median_t<uint64_t> rm = m_long_term_block_weights_cache_rolling_median; + for (size_t i = 0; i < grace_blocks; ++i) + rm.insert(0); + const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); + + // Msw: median over [100 - grace blocks] past + [grace blocks] future blocks + CHECK_AND_ASSERT_THROW_MES(grace_blocks <= 100, "Grace blocks invalid In 2021 fee scaling estimate."); + std::vector<uint64_t> weights; + get_last_n_blocks_weights(weights, 100 - grace_blocks); + weights.reserve(100); + for (size_t i = 0; i < grace_blocks; ++i) + weights.push_back(0); + const uint64_t Msw_effective_short_term_median = std::max(epee::misc_utils::median(weights), Mlw_penalty_free_zone_for_wallet); + + const uint64_t Mnw = std::min(Msw_effective_short_term_median, 50 * Mlw_penalty_free_zone_for_wallet); + + uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(m_current_block_cumul_weight_limit / 2, 1, already_generated_coins, base_reward, version)) + { + MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); + base_reward = BLOCK_REWARD_OVERESTIMATE; + } + + get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, base_reward, Mnw, Mlw_penalty_free_zone_for_wallet, fees); +} + +//------------------------------------------------------------------ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const { const uint8_t version = get_current_hard_fork_version(); @@ -3766,6 +3904,13 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; + if (version >= HF_VERSION_2021_SCALING) + { + std::vector<uint64_t> fees; + get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees); + return fees[0]; + } + const uint64_t min_block_weight = get_min_block_weight(version); std::vector<uint64_t> weights; get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); @@ -3850,9 +3995,11 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons } // The original code includes a check for the output corresponding to this input - // to be a txout_to_key. This is removed, as the database does not store this info, - // but only txout_to_key outputs are stored in the DB in the first place, done in - // Blockchain*::add_output + // to be a txout_to_key. This is removed, as the database does not store this info. + // Only txout_to_key (and since HF_VERSION_VIEW_TAGS, txout_to_tagged_key) + // outputs are stored in the DB in the first place, done in Blockchain*::add_output. + // Additional type checks on outputs were also added via cryptonote::check_output_types + // and cryptonote::get_output_public_key (see Blockchain::check_tx_outputs). m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment})); return true; @@ -4392,6 +4539,19 @@ leave: get_difficulty_for_next_block(); // just to cache it invalidate_block_template_cache(); + const uint8_t new_hf_version = get_current_hard_fork_version(); + if (new_hf_version != hf_version) + { + // the genesis block is added before everything's setup, and the txpool is empty + // when we start from scratch, so we skip this + const bool is_genesis_block = new_height == 1; + if (!is_genesis_block) + { + MGINFO("Validating txpool for v" << (unsigned)new_hf_version); + m_tx_pool.validate(new_hf_version); + } + } + send_miner_notifications(id, already_generated_coins); for (const auto& notifier: m_block_notifiers) @@ -4427,6 +4587,7 @@ bool Blockchain::check_blockchain_pruning() return m_db->check_pruning(); } //------------------------------------------------------------------ +// returns min(Mb, 1.7*Ml) as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf from HF_VERSION_LONG_TERM_BLOCK_WEIGHT uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) const { PERF_TIMER(get_next_long_term_block_weight); @@ -4441,7 +4602,18 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); uint64_t 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 = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; + uint64_t short_term_constraint; + if (hf_version >= HF_VERSION_2021_SCALING) + { + // long_term_block_weight = block_weight bounded to range [long-term-median/1.7, long-term-median*1.7] + block_weight = std::max<uint64_t>(block_weight, long_term_effective_median_block_weight * 10 / 17); + short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 7 / 10; + } + else + { + // long_term_block_weight = block_weight bounded to range [0, long-term-median*1.4] + short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; + } uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); return long_term_block_weight; @@ -4483,7 +4655,11 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti 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 + m_long_term_effective_median_block_weight * 2 / 5; + 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) @@ -4502,7 +4678,19 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); uint64_t short_term_median = epee::misc_utils::median(weights); - uint64_t effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + uint64_t effective_median_block_weight; + if (hf_version >= HF_VERSION_2021_SCALING) + { + // effective median = short_term_median bounded to range [long_term_median, 50*long_term_median], but it can't be smaller than the + // minimum penalty free zone (a.k.a. 'full reward zone') + effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(m_long_term_effective_median_block_weight, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + } + else + { + // effective median = short_term_median bounded to range [0, 50*long_term_median], but it can't be smaller than the + // minimum penalty free zone (a.k.a. 'full reward zone') + effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + } m_current_block_cumul_weight_median = effective_median_block_weight; } @@ -5021,6 +5209,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete unsigned nblocks = batches; if (i < extra) ++nblocks; + if (nblocks == 0) + break; tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, epee::span<const block>(&blocks[thread_height - height], nblocks), std::ref(maps[i])), true); thread_height += nblocks; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 9afbfbc2d..7a94f6358 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -648,6 +648,22 @@ namespace cryptonote * @return the fee estimate */ uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const; + void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const; + + /** + * @brief get four levels of dynamic per byte fee estimate for the next few blocks + * + * The dynamic fee is based on the block weight in a past window, and + * the current block reward. It is expressed per byte, and is based on + * https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf + * This function calculates an estimate for a dynamic fee which will be + * valid for the next grace_blocks + * + * @param grace_blocks number of blocks we want the fee to be valid for + * + * @return the fee estimates (4 of them) + */ + void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const; /** * @brief validate a transaction's fee diff --git a/src/cryptonote_core/blockchain_storage_boost_serialization.h b/src/cryptonote_core/blockchain_storage_boost_serialization.h index b5c14d53c..d166caf76 100644 --- a/src/cryptonote_core/blockchain_storage_boost_serialization.h +++ b/src/cryptonote_core/blockchain_storage_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 4c6536318..a78f5d673 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -879,6 +879,16 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs) + { + if (proofs.size() != 1) + return false; + const size_t sz = proofs[0].V.size(); + if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- bool core::handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block) { bool ret = true; @@ -943,6 +953,17 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; + case rct::RCTTypeBulletproofPlus: + if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) + { + MERROR_VER("Bulletproof_plus does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; default: MERROR_VER("Unknown rct type: " << rv.type); set_semantics_failed(tx_info[n].tx_hash); @@ -960,7 +981,7 @@ namespace cryptonote { if (!tx_info[n].result) continue; - if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG) + if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus) continue; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) { @@ -1156,7 +1177,8 @@ namespace cryptonote return false; } - if (!check_tx_inputs_ring_members_diff(tx)) + const uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version(); + if (!check_tx_inputs_ring_members_diff(tx, hf_version)) { MERROR_VER("tx uses duplicate ring members"); return false; @@ -1168,6 +1190,12 @@ namespace cryptonote return false; } + if (!check_output_types(tx, hf_version)) + { + MERROR_VER("tx does not use valid output type(s)"); + return false; + } + return true; } //----------------------------------------------------------------------------------------------- @@ -1274,10 +1302,9 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const + bool core::check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const { - const uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); - if (version >= 6) + if (hf_version >= 6) { for(const auto& in: tx.vin) { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index d2bffdaee..0b36730b6 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -40,7 +40,6 @@ #include "cryptonote_core/i_core_events.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "cryptonote_protocol/enums.h" -#include "storages/portable_storage_template_helper.h" #include "common/download.h" #include "common/command_line.h" #include "tx_pool.h" @@ -1013,10 +1012,11 @@ namespace cryptonote * @brief verify that each ring uses distinct members * * @param tx the transaction to check + * @param hf_version the hard fork version rules to use * * @return false if any ring uses duplicate members, true otherwise */ - bool check_tx_inputs_ring_members_diff(const transaction& tx) const; + bool check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const; /** * @brief verify that each input key image in a transaction is in diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index f41c63a4b..472026217 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -43,7 +43,6 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" -#include "multisig/multisig.h" using namespace crypto; @@ -150,12 +149,17 @@ namespace cryptonote r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); - txout_to_key tk; - tk.key = out_eph_public_key; + uint64_t amount = out_amounts[no]; + summary_amounts += amount; + + bool use_view_tags = hard_fork_version >= HF_VERSION_VIEW_TAGS; + crypto::view_tag view_tag; + if (use_view_tags) + crypto::derive_view_tag(derivation, no, view_tag); tx_out out; - summary_amounts += out.amount = out_amounts[no]; - out.target = tk; + cryptonote::set_tx_out(amount, out_eph_public_key, use_view_tags, view_tag, out); + tx.vout.push_back(out); } @@ -199,7 +203,7 @@ namespace cryptonote return addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -212,10 +216,6 @@ namespace cryptonote std::vector<rct::key> amount_keys; tx.set_null(); amount_keys.clear(); - if (msout) - { - msout->c.clear(); - } tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; @@ -329,8 +329,8 @@ namespace cryptonote return false; } - //check that derivated key is equal with real output key (if non multisig) - if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //check that derivated key is equal with real output key + if(!(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" @@ -343,7 +343,7 @@ namespace cryptonote //put key image into tx input txin_to_key input_to_key; input_to_key.amount = src_entr.amount; - input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; + input_to_key.k_image = img; //fill outputs array and use relative offsets for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) @@ -407,17 +407,16 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::public_key out_eph_public_key; + crypto::view_tag view_tag; hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key, dst_entr, change_addr, output_index, need_additional_txkeys, additional_tx_keys, - additional_tx_public_keys, amount_keys, out_eph_public_key); + additional_tx_public_keys, amount_keys, out_eph_public_key, + use_view_tags, view_tag); tx_out out; - out.amount = dst_entr.amount; - txout_to_key tk; - tk.key = out_eph_public_key; - out.target = tk; + cryptonote::set_tx_out(dst_entr.amount, out_eph_public_key, use_view_tags, view_tag, out); tx.vout.push_back(out); output_index++; summary_outs_money += dst_entr.amount; @@ -526,7 +525,6 @@ namespace cryptonote rct::keyV destinations; std::vector<uint64_t> inamounts, outamounts; std::vector<unsigned int> index; - std::vector<rct::multisig_kLRki> kLRki; for (size_t i = 0; i < sources.size(); ++i) { rct::ctkey ctkey; @@ -540,14 +538,12 @@ namespace cryptonote memwipe(&ctkey, sizeof(rct::ctkey)); // inPk: (public key, commitment) // will be done when filling in mixRing - if (msout) - { - kLRki.push_back(sources[i].multisig_kLRki); - } } for (size_t i = 0; i < tx.vout.size(); ++i) { - destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); + crypto::public_key output_public_key; + get_output_public_key(tx.vout[i], output_public_key); + destinations.push_back(rct::pk2rct(output_public_key)); outamounts.push_back(tx.vout[i].amount); amount_out += tx.vout[i].amount; } @@ -593,9 +589,9 @@ namespace cryptonote get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); rct::ctkeyV outSk; if (use_simple_rct) - tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev); + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk, rct_config, hwdev); else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey)); CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); @@ -608,7 +604,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -628,7 +624,8 @@ namespace cryptonote } } - bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout); + bool shuffle_outs = true; + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -644,7 +641,7 @@ namespace cryptonote crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}); } //--------------------------------------------------------------- bool generate_genesis_block( @@ -678,7 +675,7 @@ namespace cryptonote rx_slow_hash(main_height, seed_height, seed_hash.data, bd.data(), bd.size(), res.data, 0, 1); } - bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners) + bool get_block_longhash(const Blockchain *pbc, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners) { // block 202612 bug workaround if (height == 202612) @@ -687,8 +684,7 @@ namespace cryptonote epee::string_tools::hex_to_pod(longhash_202612, res); return true; } - blobdata bd = get_block_hashing_blob(b); - if (b.major_version >= RX_BLOCK_VERSION) + if (major_version >= RX_BLOCK_VERSION) { uint64_t seed_height, main_height; crypto::hash hash; @@ -705,12 +701,18 @@ namespace cryptonote } rx_slow_hash(main_height, seed_height, hash.data, bd.data(), bd.size(), res.data, seed_hash ? 0 : miners, !!seed_hash); } else { - const int pow_variant = b.major_version >= 7 ? b.major_version - 6 : 0; + const int pow_variant = major_version >= 7 ? major_version - 6 : 0; crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height); } return true; } + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners) + { + blobdata bd = get_block_hashing_blob(b); + return get_block_longhash(pbc, bd, res, height, b.major_version, seed_hash, miners); + } + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const int miners) { return get_block_longhash(pbc, b, res, height, NULL, miners); diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 06412d6bf..12d6b8ce5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -119,21 +119,23 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false); bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) ; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) ; bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) ; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) ; bool generate_genesis_block( block& bl @@ -142,6 +144,8 @@ namespace cryptonote ); class Blockchain; + bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, + const int major_version, const crypto::hash *seed_hash, const int miners); bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const int miners); bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners); void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, diff --git a/src/cryptonote_core/i_core_events.h b/src/cryptonote_core/i_core_events.h index 5d00858b5..629194543 100644 --- a/src/cryptonote_core/i_core_events.h +++ b/src/cryptonote_core/i_core_events.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 84605d6f5..a68da0e62 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -97,9 +97,9 @@ namespace cryptonote constexpr const std::chrono::seconds forward_delay_average{CRYPTONOTE_FORWARD_DELAY_AVERAGE}; // a kind of increasing backoff within min/max bounds - uint64_t get_relay_delay(time_t now, time_t received) + uint64_t get_relay_delay(time_t last_relay, time_t received) { - time_t d = (now - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME; + time_t d = (last_relay - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME; if (d > MAX_RELAY_TIME) d = MAX_RELAY_TIME; return d; @@ -779,7 +779,7 @@ namespace cryptonote case relay_method::local: case relay_method::fluff: case relay_method::block: - if (now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time)) + if (now - meta.last_relayed_time <= get_relay_delay(meta.last_relayed_time, meta.receive_time)) return true; // continue to next tx break; } @@ -812,7 +812,7 @@ namespace cryptonote function is only called every ~2 minutes, so this resetting should be unnecessary, but is primarily a precaution against potential changes to the callback routines. */ - elem.second.last_relayed_time = now + get_relay_delay(now, elem.second.receive_time); + elem.second.last_relayed_time = now + get_relay_delay(elem.second.last_relayed_time, elem.second.receive_time); m_blockchain.update_txpool_tx(elem.first, elem.second); } @@ -1568,61 +1568,59 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - size_t tx_weight_limit = get_transaction_weight_limit(version); - std::unordered_set<crypto::hash> remove; - m_txpool_weight = 0; - m_blockchain.for_all_txpool_txes([this, &remove, tx_weight_limit](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { - m_txpool_weight += meta.weight; - if (meta.weight > tx_weight_limit) { - LOG_PRINT_L1("Transaction " << txid << " is too big (" << meta.weight << " bytes), removing it from pool"); - remove.insert(txid); - } - else if (m_blockchain.have_tx(txid)) { - LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool"); - remove.insert(txid); - } + MINFO("Validating txpool contents for v" << (unsigned)version); + + LockedTXN lock(m_blockchain.get_db()); + + struct tx_entry_t + { + crypto::hash txid; + txpool_tx_meta_t meta; + }; + + // get all txids + std::vector<tx_entry_t> txes; + m_blockchain.for_all_txpool_txes([&txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { + if (!meta.pruned) // skip pruned txes + txes.push_back({txid, meta}); return true; }, false, relay_category::all); - size_t n_removed = 0; - if (!remove.empty()) + // take them all out and add them back in, some might fail + size_t added = 0; + for (auto &e: txes) { - LockedTXN lock(m_blockchain.get_db()); - for (const crypto::hash &txid: remove) + try { - try - { - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); - cryptonote::transaction tx; - if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary - { - MERROR("Failed to parse tx from txpool"); - continue; - } - // remove tx from db first - m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= get_transaction_weight(tx, txblob.size()); - remove_transaction_keyimages(tx, txid); - auto sorted_it = find_tx_in_sorted_container(txid); - if (sorted_it == m_txs_by_fee_and_receive_time.end()) - { - LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); - } - else - { - m_txs_by_fee_and_receive_time.erase(sorted_it); - } - ++n_removed; - } - catch (const std::exception &e) + size_t weight; + uint64_t fee; + cryptonote::transaction tx; + cryptonote::blobdata blob; + bool relayed, do_not_relay, double_spend_seen, pruned; + if (!take_tx(e.txid, tx, blob, weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) + MERROR("Failed to get tx " << e.txid << " from txpool for re-validation"); + + cryptonote::tx_verification_context tvc{}; + relay_method tx_relay = e.meta.get_relay_method(); + if (!add_tx(tx, e.txid, blob, e.meta.weight, tvc, tx_relay, relayed, version)) { - MERROR("Failed to remove invalid tx from pool"); - // continue + MINFO("Failed to re-validate tx " << e.txid << " for v" << (unsigned)version << ", dropped"); + continue; } + m_blockchain.update_txpool_tx(e.txid, e.meta); + ++added; + } + catch (const std::exception &e) + { + MERROR("Failed to re-validate tx from pool"); + continue; } - lock.commit(); } + + lock.commit(); + + const size_t n_removed = txes.size() - added; if (n_removed > 0) ++m_cookie; return n_removed; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 80b38c51d..62bef6c06 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp index ca870779e..f46ec3bbc 100644 --- a/src/cryptonote_core/tx_sanity_check.cpp +++ b/src/cryptonote_core/tx_sanity_check.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_sanity_check.h b/src/cryptonote_core/tx_sanity_check.h index 15c5476ee..9779a8e79 100644 --- a/src/cryptonote_core/tx_sanity_check.h +++ b/src/cryptonote_core/tx_sanity_check.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt index 85c25546f..b516e17e9 100644 --- a/src/cryptonote_protocol/CMakeLists.txt +++ b/src/cryptonote_protocol/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index 2f5b693dd..4e65eafa4 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h index 30fb5bc21..64ff106a3 100644 --- a/src/cryptonote_protocol/block_queue.h +++ b/src/cryptonote_protocol/block_queue.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 8c511e824..0b860f1a8 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp index 42774f2cb..92ee08054 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief This is the place to implement our handlers for protocol network actions, e.g. for ratelimit for download-requests -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -43,7 +43,6 @@ #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/thread/thread.hpp> #include "misc_language.h" -#include "pragma_comp_defs.h" #include <algorithm> diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 80dd2bc39..515b78c94 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the original cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -113,12 +113,23 @@ namespace cryptonote const block_queue &get_block_queue() const { return m_block_queue; } void stop(); void on_connection_close(cryptonote_connection_context &context); - void set_max_out_peers(unsigned int max) { m_max_out_peers = max; } + void set_max_out_peers(epee::net_utils::zone zone, unsigned int max) { CRITICAL_REGION_LOCAL(m_max_out_peers_lock); m_max_out_peers[zone] = max; } + unsigned int get_max_out_peers(epee::net_utils::zone zone) const + { + CRITICAL_REGION_LOCAL(m_max_out_peers_lock); + const auto it = m_max_out_peers.find(zone); + if (it == m_max_out_peers.end()) + { + MWARNING(epee::net_utils::zone_to_string(zone) << " max out peers not set, using default"); + return P2P_DEFAULT_CONNECTIONS_COUNT; + } + return it->second; + } bool no_sync() const { return m_no_sync; } void set_no_sync(bool value) { m_no_sync = value; } std::string get_peers_overview() const; std::pair<uint32_t, uint32_t> get_next_needed_pruning_stripe() const; - bool needs_new_sync_connections() const; + bool needs_new_sync_connections(epee::net_utils::zone zone) const; bool is_busy_syncing(); private: @@ -171,7 +182,8 @@ namespace cryptonote epee::math_helper::once_a_time_milliseconds<100> m_standby_checker; epee::math_helper::once_a_time_seconds<101> m_sync_search_checker; epee::math_helper::once_a_time_seconds<43> m_bad_peer_checker; - std::atomic<unsigned int> m_max_out_peers; + std::unordered_map<epee::net_utils::zone, unsigned int> m_max_out_peers; + mutable epee::critical_section m_max_out_peers_lock; tools::PerformanceTimer m_sync_timer, m_add_timer; uint64_t m_last_add_end_time; uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 106253082..af3031263 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the original cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -35,7 +35,6 @@ // (may contain code and/or modifications by other developers) // developer rfree: this code is caller of our new network code, and is modded; e.g. for rate limiting -#include <boost/interprocess/detail/atomic.hpp> #include <list> #include <ctime> @@ -153,6 +152,7 @@ namespace cryptonote context.m_last_request_time = boost::date_time::not_a_date_time; context.m_expect_response = 0; context.m_expect_height = 0; + context.m_requested_objects.clear(); context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download } else @@ -1776,33 +1776,49 @@ skip: return true; MTRACE("Checking for outgoing syncing peers..."); - unsigned n_syncing = 0, n_synced = 0; - boost::uuids::uuid last_synced_peer_id(boost::uuids::nil_uuid()); + std::unordered_map<epee::net_utils::zone, unsigned> n_syncing, n_synced; + std::unordered_map<epee::net_utils::zone, boost::uuids::uuid> last_synced_peer_id; + std::vector<epee::net_utils::zone> zones; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool { if (!peer_id || context.m_is_income) // only consider connected outgoing peers return true; + + const epee::net_utils::zone zone = context.m_remote_address.get_zone(); + if (n_syncing.find(zone) == n_syncing.end()) + { + n_syncing[zone] = 0; + n_synced[zone] = 0; + last_synced_peer_id[zone] = boost::uuids::nil_uuid(); + zones.push_back(zone); + } + if (context.m_state == cryptonote_connection_context::state_synchronizing) - ++n_syncing; + ++n_syncing[zone]; if (context.m_state == cryptonote_connection_context::state_normal) { - ++n_synced; + ++n_synced[zone]; if (!context.m_anchor) - last_synced_peer_id = context.m_connection_id; + last_synced_peer_id[zone] = context.m_connection_id; } return true; }); - MTRACE(n_syncing << " syncing, " << n_synced << " synced"); - // if we're at max out peers, and not enough are syncing - if (n_synced + n_syncing >= m_max_out_peers && n_syncing < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) + for (const auto& zone : zones) { - if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ - MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced"); - drop_connection(ctx, false, false); - return true; - })) - MDEBUG("Failed to find peer we wanted to drop"); + const unsigned int max_out_peers = get_max_out_peers(zone); + MTRACE("[" << epee::net_utils::zone_to_string(zone) << "] " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers"); + + // if we're at max out peers, and not enough are syncing, drop the last sync'd non-anchor + if (n_synced[zone] + n_syncing[zone] >= max_out_peers && n_syncing[zone] < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id[zone] != boost::uuids::nil_uuid()) + { + if (!m_p2p->for_connection(last_synced_peer_id[zone], [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ + MINFO(ctx << "dropping synced peer, " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers"); + drop_connection(ctx, false, false); + return true; + })) + MDEBUG("Failed to find peer we wanted to drop"); + } } return true; @@ -1987,11 +2003,13 @@ skip: ++n_peers_on_next_stripe; return true; }); + // TODO: investigate tallying by zone and comparing to max out peers by zone + const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_); const uint32_t distance = (peer_stripe + (1<<CRYPTONOTE_PRUNING_LOG_STRIPES) - next_stripe) % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES); - if ((n_out_peers >= m_max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2) + if ((n_out_peers >= max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2) { MDEBUG(context << "we want seed " << next_stripe << ", and either " << n_out_peers << " is at max out peers (" - << m_max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe << + << max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe << " is too large and we have only " << n_peers_on_next_stripe << " peers on next seed, dropping connection to make space"); return true; } @@ -2812,11 +2830,13 @@ skip: } return true; }); - const bool use_next = (n_next > m_max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0); + // TODO: investigate tallying by zone and comparing to max out peers by zone + const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_); + const bool use_next = (n_next > max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0); const uint32_t ret_stripe = use_next ? subsequent_pruning_stripe: next_pruning_stripe; MIDEBUG(const std::string po = get_peers_overview(), "get_next_needed_pruning_stripe: want height " << want_height << " (" << want_height_from_blockchain << " from blockchain, " << want_height_from_block_queue << " from block queue), stripe " << - next_pruning_stripe << " (" << n_next << "/" << m_max_out_peers << " on it and " << n_subsequent << " on " << + next_pruning_stripe << " (" << n_next << "/" << max_out_peers << " on it and " << n_subsequent << " on " << subsequent_pruning_stripe << ", " << n_others << " others) -> " << ret_stripe << " (+" << (ret_stripe - next_pruning_stripe + (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) << "), current peers " << po); @@ -2824,7 +2844,7 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections() const + bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections(epee::net_utils::zone zone) const { const uint64_t target = m_core.get_target_blockchain_height(); const uint64_t height = m_core.get_current_blockchain_height(); @@ -2832,11 +2852,11 @@ skip: return false; size_t n_out_peers = 0; m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ - if (!ctx.m_is_income) + if (!ctx.m_is_income && ctx.m_remote_address.get_zone() == zone) ++n_out_peers; return true; }); - if (n_out_peers >= m_max_out_peers) + if (n_out_peers >= get_max_out_peers(zone)) return false; return true; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index 57b1d049c..3b2ca5e37 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/enums.h b/src/cryptonote_protocol/enums.h index c0c495837..aeb66ed5c 100644 --- a/src/cryptonote_protocol/enums.h +++ b/src/cryptonote_protocol/enums.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/fwd.h b/src/cryptonote_protocol/fwd.h index e7722c2f4..f036f41cb 100644 --- a/src/cryptonote_protocol/fwd.h +++ b/src/cryptonote_protocol/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 0b065c3c3..83f37015f 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // @@ -287,6 +287,12 @@ namespace levin boost::asio::steady_timer next_epoch; boost::asio::steady_timer flush_txs; boost::asio::io_service::strand strand; + struct context_t { + std::vector<cryptonote::blobdata> fluff_txs; + std::chrono::steady_clock::time_point flush_time; + bool m_is_income; + }; + boost::unordered_map<boost::uuids::uuid, context_t> contexts; net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand` std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time @@ -363,14 +369,16 @@ namespace levin const auto now = std::chrono::steady_clock::now(); auto next_flush = std::chrono::steady_clock::time_point::max(); std::vector<std::pair<std::vector<blobdata>, boost::uuids::uuid>> connections{}; - zone_->p2p->foreach_connection([timer_error, now, &next_flush, &connections] (detail::p2p_context& context) + for (auto &e: zone_->contexts) { + auto &id = e.first; + auto &context = e.second; if (!context.fluff_txs.empty()) { if (context.flush_time <= now || timer_error) // flush on canceled timer { context.flush_time = std::chrono::steady_clock::time_point::max(); - connections.emplace_back(std::move(context.fluff_txs), context.m_connection_id); + connections.emplace_back(std::move(context.fluff_txs), id); context.fluff_txs.clear(); } else // not flushing yet @@ -378,8 +386,7 @@ namespace levin } else // nothing to flush context.flush_time = std::chrono::steady_clock::time_point::max(); - return true; - }); + } /* Always send with `fluff` flag, even over i2p/tor. The hidden service will disable the forwarding delay and immediately fluff. The i2p/tor @@ -427,22 +434,21 @@ namespace levin MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing"); - - zone->p2p->foreach_connection([txs, now, &zone, &source, &in_duration, &out_duration, &next_flush] (detail::p2p_context& context) + for (auto &e: zone->contexts) { + auto &id = e.first; + auto &context = e.second; // When i2p/tor, only fluff to outbound connections - if (context.handshake_complete() && source != context.m_connection_id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) + if (source != id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) { if (context.fluff_txs.empty()) context.flush_time = now + (context.m_is_income ? in_duration() : out_duration()); next_flush = std::min(next_flush, context.flush_time); context.fluff_txs.reserve(context.fluff_txs.size() + txs.size()); - for (const blobdata& tx : txs) - context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns) + context.fluff_txs.insert(context.fluff_txs.end(), txs.begin(), txs.end()); } - return true; - }); + } if (next_flush == std::chrono::steady_clock::time_point::max()) MWARNING("Unable to send transaction(s), no available connections"); @@ -749,6 +755,32 @@ namespace levin ); } + void notify::on_handshake_complete(const boost::uuids::uuid &id, bool is_income) + { + if (!zone_) + return; + + auto& zone = zone_; + zone_->strand.dispatch([zone, id, is_income]{ + zone->contexts[id] = { + .fluff_txs = {}, + .flush_time = std::chrono::steady_clock::time_point::max(), + .m_is_income = is_income, + }; + }); + } + + void notify::on_connection_close(const boost::uuids::uuid &id) + { + if (!zone_) + return; + + auto& zone = zone_; + zone_->strand.dispatch([zone, id]{ + zone->contexts.erase(id); + }); + } + void notify::run_epoch() { if (!zone_) diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index abbf9d461..2927eea86 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // @@ -101,6 +101,9 @@ namespace levin //! Probe for new outbound connection - skips if not needed. void new_out_connection(); + void on_handshake_complete(const boost::uuids::uuid &id, bool is_income); + void on_connection_close(const boost::uuids::uuid &id); + //! Run the logic for the next epoch immediately. Only use in testing. void run_epoch(); diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index b95c0ac88..455f72472 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -37,30 +37,7 @@ set(daemon_sources set(daemon_headers) -set(daemon_private_headers - command_parser_executor.h - command_server.h - core.h - daemon.h - executor.h - p2p.h - protocol.h - rpc.h - rpc_command_executor.h - - # cryptonote_protocol - ../cryptonote_protocol/cryptonote_protocol_defs.h - ../cryptonote_protocol/cryptonote_protocol_handler.h - ../cryptonote_protocol/cryptonote_protocol_handler.inl - ../cryptonote_protocol/cryptonote_protocol_handler_common.h - - # p2p - ../p2p/net_node.h - ../p2p/net_node_common.h - ../p2p/net_peerlist.h - ../p2p/net_peerlist_boost_serialization.h - ../p2p/p2p_protocol_defs.h - ../p2p/stdafx.h) +monero_find_all_headers(daemon_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(daemon ${daemon_private_headers}) diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index a988fe25f..96fddc02d 100644 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 14233bf29..20c906141 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 64e4c301b..cd9e6544e 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -40,7 +40,6 @@ #include "daemon/rpc_command_executor.h" #include "common/common_fwd.h" -#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" namespace daemonize { diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 63f44c4cd..fc5f1b3d7 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index df7198d04..babb8e85a 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -9,7 +9,7 @@ Passing RPC commands: */ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -43,7 +43,6 @@ Passing RPC commands: #include "common/common_fwd.h" #include "console_handler.h" #include "daemon/command_parser_executor.h" -#include "net/net_fwd.h" namespace daemonize { diff --git a/src/daemon/core.h b/src/daemon/core.h index 0811cf420..fde0d6bab 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3f1885423..286c02b50 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 2eb2019ce..529b42d20 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp index f9ba3b493..d67bd6141 100644 --- a/src/daemon/executor.cpp +++ b/src/daemon/executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/executor.h b/src/daemon/executor.h index a7235711c..d59ef22fb 100644 --- a/src/daemon/executor.h +++ b/src/daemon/executor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 70aec5538..73d9ebce1 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h index 38862c017..799dc3d25 100644 --- a/src/daemon/p2p.h +++ b/src/daemon/p2p.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 6b03c169a..733bacfc9 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h index bff7dc449..7b059ebd3 100644 --- a/src/daemon/rpc.h +++ b/src/daemon/rpc.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 16e6a304c..b6364ff77 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 118f04731..ebd7eda85 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -43,7 +43,6 @@ #include "common/common_fwd.h" #include "common/rpc_client.h" #include "cryptonote_basic/cryptonote_basic.h" -#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/daemonizer/CMakeLists.txt b/src/daemonizer/CMakeLists.txt index 462d40531..61999b3a5 100644 --- a/src/daemonizer/CMakeLists.txt +++ b/src/daemonizer/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/daemonizer/daemonizer.h b/src/daemonizer/daemonizer.h index 442861cdb..fa19c28b0 100644 --- a/src/daemonizer/daemonizer.h +++ b/src/daemonizer/daemonizer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl index 76ef467ca..bd2741039 100644 --- a/src/daemonizer/posix_daemonizer.inl +++ b/src/daemonizer/posix_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h index d841df0fc..b1d82ff43 100644 --- a/src/daemonizer/posix_fork.h +++ b/src/daemonizer/posix_fork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index 67c8a2855..a0086408f 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp index 01be6b247..846f6c071 100644 --- a/src/daemonizer/windows_service.cpp +++ b/src/daemonizer/windows_service.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service.h b/src/daemonizer/windows_service.h index 091041391..a96674d19 100644 --- a/src/daemonizer/windows_service.h +++ b/src/daemonizer/windows_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service_runner.h b/src/daemonizer/windows_service_runner.h index eb72df66d..a8e4d3b0e 100644 --- a/src/daemonizer/windows_service_runner.h +++ b/src/daemonizer/windows_service_runner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/debug_utilities/CMakeLists.txt b/src/debug_utilities/CMakeLists.txt index 7b21123f6..ecb0f0229 100644 --- a/src/debug_utilities/CMakeLists.txt +++ b/src/debug_utilities/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index c039b93c5..41c397bd8 100644 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/debug_utilities/dns_checks.cpp b/src/debug_utilities/dns_checks.cpp index 138cd4fc1..caa0421e9 100644 --- a/src/debug_utilities/dns_checks.cpp +++ b/src/debug_utilities/dns_checks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp index bffff0882..40b651ab3 100644 --- a/src/debug_utilities/object_sizes.cpp +++ b/src/debug_utilities/object_sizes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index 3597ab336..e4f1159b5 100644 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/device/device.cpp b/src/device/device.cpp index 4821abdcf..e6cd358b6 100644 --- a/src/device/device.cpp +++ b/src/device/device.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/device.hpp b/src/device/device.hpp index 6005e157d..eca91006f 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -222,7 +222,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) = 0; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) = 0; virtual bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) = 0; virtual bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) = 0; diff --git a/src/device/device_cold.hpp b/src/device/device_cold.hpp index d435b448c..ba4d6d8ae 100644 --- a/src/device/device_cold.hpp +++ b/src/device/device_cold.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -162,6 +162,26 @@ namespace hw { * Live refresh process termination */ virtual void live_refresh_finish() =0; + + /** + * Requests public address, uses empty passphrase if asked for. + */ + virtual bool get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) =0; + + /** + * Reset session ID, restart with a new session. + */ + virtual void reset_session() =0; + + /** + * Returns true if device already asked for passphrase entry before (i.e., obviously supports passphrase entry) + */ + virtual bool seen_passphrase_entry_prompt() =0; + + /** + * Uses empty passphrase for all passphrase queries. + */ + virtual void set_use_empty_passphrase(bool always_use_empty_passphrase) =0; }; } diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index 145197212..d70ece229 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -263,6 +263,11 @@ namespace hw { return true; } + bool device_default::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) { + crypto::derive_view_tag(derivation, output_index, view_tag); + return true; + } + bool device_default::conceal_derivation(crypto::key_derivation &derivation, const crypto::public_key &tx_pub_key, const std::vector<crypto::public_key> &additional_tx_pub_keys, const crypto::key_derivation &main_derivation, const std::vector<crypto::key_derivation> &additional_derivations){ return true; } @@ -291,7 +296,8 @@ namespace hw { const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, - std::vector<rct::key> &amount_keys, crypto::public_key &out_eph_public_key) { + std::vector<rct::key> &amount_keys, crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) { crypto::key_derivation derivation; @@ -331,6 +337,12 @@ namespace hw { derivation_to_scalar(derivation, output_index, scalar1); amount_keys.push_back(rct::sk2rct(scalar1)); } + + if (use_view_tags) + { + derive_view_tag(derivation, output_index, view_tag); + } + r = derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 2493bd67d..7d3543652 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -101,6 +101,7 @@ namespace hw { bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override; bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override; bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override; + bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag); /* ======================================================================= */ @@ -126,7 +127,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) override; + crypto::public_key &out_eph_public_key, + bool use_view_tags, crypto::view_tag &view_tag) override; bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) override; bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) override; diff --git a/src/device/device_io.hpp b/src/device/device_io.hpp index 6a7744c11..b333caa13 100644 --- a/src/device/device_io.hpp +++ b/src/device/device_io.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index 7aa5b39bf..3116a8713 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp index e6d76f276..fd2ec8515 100644 --- a/src/device/device_io_hid.hpp +++ b/src/device/device_io_hid.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 5caad3a1a..aa73e998c 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -55,10 +55,10 @@ namespace hw { } #define TRACKD MTRACE("hw") - #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(mask))==(ok), \ + #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(msk))==(ok), \ "Wrong Device Status: " << "0x" << std::hex << (sw) << " (" << Status::to_string(sw) << "), " << \ "EXPECTED 0x" << std::hex << (ok) << " (" << Status::to_string(ok) << "), " << \ - "MASK 0x" << std::hex << (mask)); + "MASK 0x" << std::hex << (msk)); #define ASSERT_T0(exp) CHECK_AND_ASSERT_THROW_MES(exp, "Protocol assert failure: "#exp ) ; #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg); @@ -451,13 +451,6 @@ namespace hw { ASSERT_X(this->length_recv>=3, "Communication error, less than three bytes received. Check your application version."); - unsigned int device_version = 0; - device_version = VERSION(this->buffer_recv[0], this->buffer_recv[1], this->buffer_recv[2]); - - ASSERT_X (device_version >= MINIMAL_APP_VERSION, - "Unsupported device application version: " << VERSION_MAJOR(device_version)<<"."<<VERSION_MINOR(device_version)<<"."<<VERSION_MICRO(device_version) << - " At least " << MINIMAL_APP_VERSION_MAJOR<<"."<<MINIMAL_APP_VERSION_MINOR<<"."<<MINIMAL_APP_VERSION_MICRO<<" is required."); - return true; } @@ -470,7 +463,10 @@ namespace hw { this->length_recv -= 2; this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1]; logRESP(); - ASSERT_SW(this->sw,ok,msk); + MDEBUG("Device "<< this->id << " exchange: sw: " << this->sw << " expected: " << ok); + ASSERT_X(sw != SW_CLIENT_NOT_SUPPORTED, "Monero Ledger App doesn't support current monero version. Try to update the Monero Ledger App, at least " << MINIMAL_APP_VERSION_MAJOR<< "." << MINIMAL_APP_VERSION_MINOR << "." << MINIMAL_APP_VERSION_MICRO << " is required."); + ASSERT_X(sw != SW_PROTOCOL_NOT_SUPPORTED, "Make sure no other program is communicating with the Ledger."); + ASSERT_SW(this->sw,ok,mask); return this->sw; } @@ -487,7 +483,7 @@ namespace hw { // cancel on device deny = 1; } else { - ASSERT_SW(this->sw,ok,msk); + ASSERT_SW(this->sw,ok,mask); } logRESP(); @@ -528,6 +524,7 @@ namespace hw { static const std::vector<hw::io::hid_conn_params> known_devices { {0x2c97, 0x0001, 0, 0xffa0}, {0x2c97, 0x0004, 0, 0xffa0}, + {0x2c97, 0x0005, 0, 0xffa0}, }; bool device_ledger::connect(void) { @@ -697,7 +694,8 @@ namespace hw { log_hexbuffer("derive_subaddress_public_key: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("derive_subaddress_public_key: [[IN]] derivation", derivation_x.data, 32); log_message ("derive_subaddress_public_key: [[IN]] index ", std::to_string((int)output_index_x)); - this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x); + if (!this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x)) + return false; log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32); #endif @@ -705,7 +703,8 @@ namespace hw { //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help //of the device), so continue that way. MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey"); - crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub); + if (!crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub)) + return false; } else { AUTO_LOCK_CMD(); int offset = set_command_header_noopt(INS_DERIVE_SUBADDRESS_PUBLIC_KEY); @@ -1055,7 +1054,8 @@ namespace hw { crypto::key_derivation derivation_x; log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32); - this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x); + if (!this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x)) + return false; log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32); #endif @@ -1210,7 +1210,8 @@ namespace hw { log_hexbuffer("derive_public_key: [[IN]] derivation ", derivation_x.data, 32); log_message ("derive_public_key: [[IN]] output_index", std::to_string(output_index_x)); log_hexbuffer("derive_public_key: [[IN]] pub ", pub_x.data, 32); - this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x); + if (!this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x)) + return false; log_hexbuffer("derive_public_key: [[OUT]] derived_pub ", derived_pub_x.data, 32); #endif @@ -1531,7 +1532,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) { + crypto::public_key &out_eph_public_key, + bool use_view_tags, crypto::view_tag &view_tag) { AUTO_LOCK_CMD(); #ifdef DEBUG_HWDEVICE @@ -1545,6 +1547,8 @@ namespace hw { const boost::optional<cryptonote::account_public_address> change_addr_x = change_addr; const size_t output_index_x = output_index; const bool need_additional_txkeys_x = need_additional_txkeys; + const bool use_view_tags_x = use_view_tags; + const crypto::view_tag view_tag_x = view_tag; std::vector<crypto::secret_key> additional_tx_keys_x; for (const auto &k: additional_tx_keys) { @@ -1572,7 +1576,7 @@ namespace hw { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32); } this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x, - additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x); + additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x, use_view_tags_x, view_tag_x); if(need_additional_txkeys_x) { log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32); } diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 3b6cc505f..074bfaa8d 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -166,8 +166,6 @@ namespace hw { void send_secret(const unsigned char sec[32], int &offset); void receive_secret(unsigned char sec[32], int &offset); - // hw running mode - device_mode mode; bool tx_in_progress; // map public destination key to ephemeral destination key @@ -275,7 +273,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) override; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) override; bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) override; bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) override; diff --git a/src/device/log.cpp b/src/device/log.cpp index 6e62f1dee..9b882b784 100644 --- a/src/device/log.cpp +++ b/src/device/log.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device/log.hpp b/src/device/log.hpp index 66c3e06db..660adc63e 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index f105f68b7..6688a317b 100644 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index c2070b0d1..6f7ae9a6b 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -66,8 +66,8 @@ namespace trezor { device_trezor::~device_trezor() { try { - disconnect(); - release(); + device_trezor::disconnect(); + device_trezor::release(); } catch(std::exception const& e){ MWARNING("Could not disconnect and release: " << e.what()); } @@ -178,6 +178,15 @@ namespace trezor { } } + bool device_trezor::get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) { + m_reply_with_empty_passphrase = true; + const auto empty_passphrase_reverter = epee::misc_utils::create_scope_leave_handler([&]() { + m_reply_with_empty_passphrase = false; + }); + + return get_public_address(pubkey); + } + bool device_trezor::get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) { try { MDEBUG("Loading view-only key from the Trezor. Please check the Trezor for a confirmation."); @@ -206,6 +215,18 @@ namespace trezor { get_address(index, payment_id, true); } + void device_trezor::reset_session() { + m_device_session_id.clear(); + } + + bool device_trezor::seen_passphrase_entry_prompt() { + return m_seen_passphrase_entry_message; + } + + void device_trezor::set_use_empty_passphrase(bool always_use_empty_passphrase) { + m_always_use_empty_passphrase = always_use_empty_passphrase; + } + /* ======================================================================= */ /* Helpers */ /* ======================================================================= */ diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index d91d1de3f..38aeaf6b5 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -205,6 +205,26 @@ namespace trezor { const ::tools::wallet2::unsigned_tx_set & unsigned_tx, ::tools::wallet2::signed_tx_set & signed_tx, hw::tx_aux_data & aux_data) override; + + /** + * Requests public address, uses empty passphrase if asked for. + */ + bool get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) override; + + /** + * Reset session ID, restart with a new session. + */ + virtual void reset_session() override; + + /** + * Returns true if device already asked for passphrase entry before (i.e., obviously supports passphrase entry) + */ + bool seen_passphrase_entry_prompt() override; + + /** + * Uses empty passphrase for all passphrase queries. + */ + void set_use_empty_passphrase(bool use_always_empty_passphrase) override; }; #endif diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index b0b4342f5..56f3784a7 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -45,7 +45,10 @@ namespace trezor { const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080}; - device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success) { + device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success), + m_reply_with_empty_passphrase(false), + m_always_use_empty_passphrase(false), + m_seen_passphrase_entry_message(false) { #ifdef WITH_TREZOR_DEBUGGING m_debug = false; #endif @@ -155,6 +158,9 @@ namespace trezor { TREZOR_AUTO_LOCK_DEVICE(); m_device_session_id.clear(); m_features.reset(); + m_seen_passphrase_entry_message = false; + m_reply_with_empty_passphrase = false; + m_always_use_empty_passphrase = false; if (m_transport){ try { @@ -476,6 +482,7 @@ namespace trezor { 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. @@ -491,19 +498,21 @@ namespace trezor { } boost::optional<epee::wipeable_string> passphrase; - TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, on_device); + if (m_reply_with_empty_passphrase || m_always_use_empty_passphrase) { + MDEBUG("Answering passphrase prompt with an empty passphrase, always use empty: " << m_always_use_empty_passphrase); + on_device = false; + passphrase = epee::wipeable_string(""); + } else if (m_passphrase){ + MWARNING("Answering passphrase prompt with a stored passphrase (do not use; passphrase can be seen by a potential malware / attacker)"); + on_device = false; + passphrase = epee::wipeable_string(m_passphrase.get()); + } else { + TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, on_device); + } messages::common::PassphraseAck m; m.set_on_device(on_device); if (!on_device) { - if (!passphrase && m_passphrase) { - passphrase = m_passphrase; - } - - if (m_passphrase) { - m_passphrase = boost::none; - } - if (passphrase) { m.set_allocated_passphrase(new std::string(passphrase->data(), passphrase->size())); } diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 0162b23df..5b6920313 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -101,6 +101,9 @@ namespace trezor { messages::MessageType m_last_msg_type; cryptonote::network_type network_type; + bool m_reply_with_empty_passphrase; + bool m_always_use_empty_passphrase; + bool m_seen_passphrase_entry_message; #ifdef WITH_TREZOR_DEBUGGING std::shared_ptr<trezor_debug_callback> m_debug_callback; diff --git a/src/device_trezor/trezor.hpp b/src/device_trezor/trezor.hpp index b3f85f6a8..f2a352f58 100644 --- a/src/device_trezor/trezor.hpp +++ b/src/device_trezor/trezor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/debug_link.cpp b/src/device_trezor/trezor/debug_link.cpp index 102d1f966..1eed0a53e 100644 --- a/src/device_trezor/trezor/debug_link.cpp +++ b/src/device_trezor/trezor/debug_link.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/debug_link.hpp b/src/device_trezor/trezor/debug_link.hpp index a5f05ea94..b7a252833 100644 --- a/src/device_trezor/trezor/debug_link.hpp +++ b/src/device_trezor/trezor/debug_link.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/exceptions.hpp b/src/device_trezor/trezor/exceptions.hpp index 9d63329e4..818b2cb6c 100644 --- a/src/device_trezor/trezor/exceptions.hpp +++ b/src/device_trezor/trezor/exceptions.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/messages_map.cpp b/src/device_trezor/trezor/messages_map.cpp index 02869f760..313fd6820 100644 --- a/src/device_trezor/trezor/messages_map.cpp +++ b/src/device_trezor/trezor/messages_map.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/messages_map.hpp b/src/device_trezor/trezor/messages_map.hpp index a903eaf92..b1ea65e6e 100644 --- a/src/device_trezor/trezor/messages_map.hpp +++ b/src/device_trezor/trezor/messages_map.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 92150b579..a400e82c7 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -154,8 +154,7 @@ namespace ki { res.emplace_back(); auto & cres = res.back(); - - cres.set_out_key(key_to_string(boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key)); + cres.set_out_key(key_to_string(td.get_public_key())); cres.set_tx_pub_key(key_to_string(tx_pub_key)); cres.set_internal_output_index(td.m_internal_output_index); cres.set_sub_addr_major(td.m_subaddr_index.major); diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index 0fdd36a51..858db1520 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 881848a80..53b35a37a 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp index ff843f06b..a452724da 100644 --- a/src/device_trezor/trezor/transport.hpp +++ b/src/device_trezor/trezor/transport.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/trezor_defs.hpp b/src/device_trezor/trezor/trezor_defs.hpp index 9882d6d4b..53bf2b03c 100644 --- a/src/device_trezor/trezor/trezor_defs.hpp +++ b/src/device_trezor/trezor/trezor_defs.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt index 6d8bdfb5c..e8aaec62c 100644 --- a/src/gen_multisig/CMakeLists.txt +++ b/src/gen_multisig/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2020, The Monero Project +# Copyright (c) 2017-2022, The Monero Project # # All rights reserved. # diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 4aa21b149..f13e74b0f 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -95,55 +95,35 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str } // gather the keys - std::vector<crypto::secret_key> sk(total); - std::vector<crypto::public_key> pk(total); + std::vector<std::string> first_round_msgs; + first_round_msgs.reserve(total); for (size_t n = 0; n < total; ++n) { wallets[n]->decrypt_keys(pwd_container->password()); - if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) - { - tools::fail_msg_writer() << genms::tr("Failed to verify multisig info"); - return false; - } + + first_round_msgs.emplace_back(wallets[n]->get_multisig_first_kex_msg()); + wallets[n]->encrypt_keys(pwd_container->password()); } // make the wallets multisig - std::vector<std::string> extra_info(total); + std::vector<std::string> kex_msgs_intermediate(total); std::stringstream ss; for (size_t n = 0; n < total; ++n) { std::string name = basename + "-" + std::to_string(n + 1); - std::vector<crypto::secret_key> skn; - std::vector<crypto::public_key> pkn; - for (size_t k = 0; k < total; ++k) - { - if (k != n) - { - skn.push_back(sk[k]); - pkn.push_back(pk[k]); - } - } - extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + + kex_msgs_intermediate[n] = wallets[n]->make_multisig(pwd_container->password(), first_round_msgs, threshold); + ss << " " << name << std::endl; } //exchange keys unless exchange_multisig_keys returns no extra info - while (!extra_info[0].empty()) + while (!kex_msgs_intermediate[0].empty()) { - std::unordered_set<crypto::public_key> pkeys; - std::vector<crypto::public_key> signers(total); - for (size_t n = 0; n < total; ++n) - { - if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n])) - { - tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info"); - return false; - } - } for (size_t n = 0; n < total; ++n) { - extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers); + kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate); } } diff --git a/src/gen_ssl_cert/CMakeLists.txt b/src/gen_ssl_cert/CMakeLists.txt index 6203feb21..efadc7c31 100644 --- a/src/gen_ssl_cert/CMakeLists.txt +++ b/src/gen_ssl_cert/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2020, The Monero Project +# Copyright (c) 2017-2022, The Monero Project # # All rights reserved. # diff --git a/src/gen_ssl_cert/gen_ssl_cert.cpp b/src/gen_ssl_cert/gen_ssl_cert.cpp index 1a048e9e8..cd810ed20 100644 --- a/src/gen_ssl_cert/gen_ssl_cert.cpp +++ b/src/gen_ssl_cert/gen_ssl_cert.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/hardforks/CMakeLists.txt b/src/hardforks/CMakeLists.txt index 46f51e09d..81a3d694b 100644 --- a/src/hardforks/CMakeLists.txt +++ b/src/hardforks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -29,8 +29,7 @@ set(hardforks_sources hardforks.cpp) -set(hardforks_headers - hardforks.h) +monero_find_all_headers(hardforks_headers "${CMAKE_CURRENT_SOURCE_DIR}") set(hardforks_private_headers) diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 9055b92e3..0dd0cf5f0 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -70,6 +70,9 @@ const hardfork_t mainnet_hard_forks[] = { { 13, 2210000, 0, 1598180817 }, { 14, 2210720, 0, 1598180818 }, + + { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules + { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -95,6 +98,8 @@ const hardfork_t testnet_hard_forks[] = { { 12, 1308737, 0, 1569582000 }, { 13, 1543939, 0, 1599069376 }, { 14, 1544659, 0, 1599069377 }, + { 15, 1982800, 0, 1652727000 }, + { 16, 1983520, 0, 1652813400 }, }; const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); const uint64_t testnet_hard_fork_version_1_till = 624633; diff --git a/src/hardforks/hardforks.h b/src/hardforks/hardforks.h index 039f18176..53f14b8eb 100644 --- a/src/hardforks/hardforks.h +++ b/src/hardforks/hardforks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/lmdb/CMakeLists.txt b/src/lmdb/CMakeLists.txt index 1f369f114..a26c48ad5 100644 --- a/src/lmdb/CMakeLists.txt +++ b/src/lmdb/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2018, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -27,7 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(lmdb_sources database.cpp error.cpp table.cpp value_stream.cpp) -set(lmdb_headers database.h error.h key_stream.h table.h transaction.h util.h value_stream.h) +monero_find_all_headers(lmdb_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_add_library(lmdb_lib ${lmdb_sources} ${lmdb_headers}) target_link_libraries(lmdb_lib common ${LMDB_LIBRARY}) diff --git a/src/lmdb/database.cpp b/src/lmdb/database.cpp index ccab1902a..544197d57 100644 --- a/src/lmdb/database.cpp +++ b/src/lmdb/database.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/database.h b/src/lmdb/database.h index 269f8c8a1..0c2390652 100644 --- a/src/lmdb/database.h +++ b/src/lmdb/database.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/error.cpp b/src/lmdb/error.cpp index 91479521e..62fdb83c3 100644 --- a/src/lmdb/error.cpp +++ b/src/lmdb/error.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/error.h b/src/lmdb/error.h index 2944adf78..f4134359b 100644 --- a/src/lmdb/error.h +++ b/src/lmdb/error.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/key_stream.h b/src/lmdb/key_stream.h index 40434d3a1..11fa284dd 100644 --- a/src/lmdb/key_stream.h +++ b/src/lmdb/key_stream.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/table.cpp b/src/lmdb/table.cpp index 0818b74e6..725a1a0b7 100644 --- a/src/lmdb/table.cpp +++ b/src/lmdb/table.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/transaction.h b/src/lmdb/transaction.h index cdd80696c..f358290ec 100644 --- a/src/lmdb/transaction.h +++ b/src/lmdb/transaction.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/util.h b/src/lmdb/util.h index 50162b7c8..c6c75bc00 100644 --- a/src/lmdb/util.h +++ b/src/lmdb/util.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/value_stream.cpp b/src/lmdb/value_stream.cpp index 604140e47..6a1e054c1 100644 --- a/src/lmdb/value_stream.cpp +++ b/src/lmdb/value_stream.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/value_stream.h b/src/lmdb/value_stream.h index 01090aa67..bd2814ef4 100644 --- a/src/lmdb/value_stream.h +++ b/src/lmdb/value_stream.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index ed53a41f6..738633ef5 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -31,23 +31,7 @@ set(mnemonics_sources set(mnemonics_headers) -set(mnemonics_private_headers - electrum-words.h - chinese_simplified.h - english.h - dutch.h - french.h - german.h - italian.h - japanese.h - language_base.h - english_old.h - portuguese.h - russian.h - singleton.h - spanish.h - esperanto.h - lojban.h) +monero_find_all_headers(mnemonics_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(mnemonics ${mnemonics_private_headers}) diff --git a/src/mnemonics/chinese_simplified.h b/src/mnemonics/chinese_simplified.h index 7a211bc34..2661b5820 100644 --- a/src/mnemonics/chinese_simplified.h +++ b/src/mnemonics/chinese_simplified.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2020, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/dutch.h b/src/mnemonics/dutch.h index 29c346d42..ace2e1b3d 100644 --- a/src/mnemonics/dutch.h +++ b/src/mnemonics/dutch.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 8c79a53ca..b53f3acd3 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index eb0c99e0b..becd6bb4e 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/english.h b/src/mnemonics/english.h index b7eb3d643..1715445d8 100644 --- a/src/mnemonics/english.h +++ b/src/mnemonics/english.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/english_old.h b/src/mnemonics/english_old.h index f408b2a6c..c609dff2d 100644 --- a/src/mnemonics/english_old.h +++ b/src/mnemonics/english_old.h @@ -1,6 +1,6 @@ // Word list originally created as part of the Electrum project, Copyright (C) 2014 Thomas Voegtlin
//
-// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/esperanto.h b/src/mnemonics/esperanto.h index 4897fe0b3..1d3437fad 100644 --- a/src/mnemonics/esperanto.h +++ b/src/mnemonics/esperanto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/french.h b/src/mnemonics/french.h index d5a7b6cb6..be2f8957c 100644 --- a/src/mnemonics/french.h +++ b/src/mnemonics/french.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/german.h b/src/mnemonics/german.h index c38a1fb83..4f5265888 100644 --- a/src/mnemonics/german.h +++ b/src/mnemonics/german.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/italian.h b/src/mnemonics/italian.h index f202e23e2..dbd39984b 100644 --- a/src/mnemonics/italian.h +++ b/src/mnemonics/italian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/japanese.h b/src/mnemonics/japanese.h index 899d78f48..5831b1995 100644 --- a/src/mnemonics/japanese.h +++ b/src/mnemonics/japanese.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2020, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index 1aa869e45..92eb09f0d 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/lojban.h b/src/mnemonics/lojban.h index 95d8b4633..68aefd5fd 100644 --- a/src/mnemonics/lojban.h +++ b/src/mnemonics/lojban.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/portuguese.h b/src/mnemonics/portuguese.h index 090278f27..9ddac09bb 100644 --- a/src/mnemonics/portuguese.h +++ b/src/mnemonics/portuguese.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2020, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/russian.h b/src/mnemonics/russian.h index ca2629c1a..8922b1ed9 100644 --- a/src/mnemonics/russian.h +++ b/src/mnemonics/russian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor sammy007
//
-// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/singleton.h b/src/mnemonics/singleton.h index 10d220b65..91faad92c 100644 --- a/src/mnemonics/singleton.h +++ b/src/mnemonics/singleton.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project
+// Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/spanish.h b/src/mnemonics/spanish.h index f3adaaf42..1bdb6b934 100644 --- a/src/mnemonics/spanish.h +++ b/src/mnemonics/spanish.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2020, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index eaa2c6f71..61e658a39 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2020, The Monero Project +# Copyright (c) 2017-2022, The Monero Project # # All rights reserved. # @@ -27,12 +27,16 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(multisig_sources - multisig.cpp) + multisig.cpp + multisig_account.cpp + multisig_account_kex_impl.cpp + multisig_clsag_context.cpp + multisig_kex_msg.cpp + multisig_tx_builder_ringct.cpp) set(multisig_headers) -set(multisig_private_headers - multisig.h) +monero_find_all_headers(multisig_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(multisig ${multisig_private_headers}) @@ -46,6 +50,7 @@ target_link_libraries(multisig PUBLIC ringct cryptonote_basic + cryptonote_core common cncrypto PRIVATE diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 272de73b2..fabffdd02 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -26,29 +26,34 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include <unordered_set> -#include "include_base_utils.h" #include "crypto/crypto.h" -#include "ringct/rctOps.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_format_utils.h" -#include "multisig.h" #include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "ringct/rctOps.h" + +#include <algorithm> +#include <unordered_map> +#include <unordered_set> +#include <vector> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "multisig" -using namespace std; - -namespace cryptonote +namespace multisig { - //----------------------------------------------------------------- + //---------------------------------------------------------------------------------------------------------------------- crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { + CHECK_AND_ASSERT_THROW_MES(key != crypto::null_skey, "Unexpected null secret key (danger!)."); + rct::key multisig_salt; static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size"); memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key)); + // private key = H(key, domain-sep) rct::keyV data; data.reserve(2); data.push_back(rct::sk2rct(key)); @@ -57,134 +62,79 @@ namespace cryptonote memwipe(&data[0], sizeof(rct::key)); return result; } - //----------------------------------------------------------------- - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - // the multisig spend public key is the sum of all spend public keys - multisig_keys.clear(); - const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key"); - for (const auto &k: spend_keys) - rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(spend_secret_key); - spend_skey = rct::sk2rct(spend_secret_key); - } - //----------------------------------------------------------------- - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - multisig_keys.clear(); - spend_pkey = rct::identity(); - spend_skey = rct::zero(); - - // create all our composite private keys - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: spend_keys) - { - rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk)); - memwipe(&sk, sizeof(sk)); - multisig_keys.push_back(msk); - sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data); - } - } - //----------------------------------------------------------------- - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations) - { - std::vector<crypto::public_key> multisig_keys; - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: derivations) - { - rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - multisig_keys.push_back(rct::rct2pk(d)); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys) - { - rct::key secret_key = rct::zero(); - for (const auto &k: multisig_keys) - { - sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data); - } - - return rct::rct2sk(secret_key); - } - //----------------------------------------------------------------- - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations) - { - std::vector<crypto::secret_key> multisig_keys; - multisig_keys.reserve(derivations.size()); - - for (const auto &k: derivations) - { - multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k)))); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys) - { - crypto::secret_key view_skey = get_multisig_blinded_secret_key(skey); - for (const auto &k: skeys) - sc_add((unsigned char*)&view_skey, rct::sk2rct(view_skey).bytes, rct::sk2rct(k).bytes); - return view_skey; - } - //----------------------------------------------------------------- - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys) - { - rct::key spend_public_key = rct::identity(); - for (const auto &pk: pkeys) - { - rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); - } - return rct::rct2pk(spend_public_key); - } - //----------------------------------------------------------------- - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki) { if (multisig_key_index >= keys.m_multisig_keys.size()) return false; crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); return true; } - //----------------------------------------------------------------- - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R) + //---------------------------------------------------------------------------------------------------------------------- + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R) { rct::scalarmultBase((rct::key&)L, rct::sk2rct(k)); crypto::generate_key_image(pkey, k, (crypto::key_image&)R); } - //----------------------------------------------------------------- - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki) { + // create a multisig partial key image + // KI_partial = ([view key component] + [subaddress component] + [multisig privkeys]) * Hp(output one-time address) + // - the 'multisig priv keys' here are those held by the local account + // - later, we add in the components held by other participants cryptonote::keypair in_ephemeral; if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki, keys.get_device())) return false; std::unordered_set<crypto::key_image> used; - for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) + + // create a key image component for each of the local account's multisig private keys + for (std::size_t m = 0; m < keys.m_multisig_keys.size(); ++m) { crypto::key_image pki; - bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); + // pki = keys.m_multisig_keys[m] * Hp(out_key) + // pki = key image component + // out_key = one-time address of an output owned by the multisig group + bool r = generate_multisig_key_image(keys, m, out_key, pki); if (!r) return false; + + // this KI component is 'used' because it was included in the partial key image 'ki' above used.insert(pki); } + + // add the KI components from other participants to the partial KI + // if they not included yet for (const auto &pki: pkis) { if (used.find(pki) == used.end()) { + // ignore components that have already been 'used' used.insert(pki); + + // KI_partial = KI_partial + KI_component[...] rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); } } + + // at the end, 'ki' will hold the true key image for our output if inputs were sufficient + // - if 'pkis' (the other participants' KI components) is missing some components + // then 'ki' will not be complete + return true; } - //----------------------------------------------------------------- - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold) - { - CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold"); - return participants - threshold + 1; - } -} + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index eab32187c..16dbbc544 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -28,44 +28,42 @@ #pragma once -#include <vector> -#include <unordered_map> #include "crypto/crypto.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "ringct/rctTypes.h" -namespace cryptonote -{ - struct account_keys; +#include <unordered_map> +#include <unordered_set> +#include <vector> - crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - /** - * @brief generate_multisig_derivations performs common DH key derivation. - * Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant. - * this functions does the following: new multisig key = secret spend * public multisig key - * @param keys - current wallet's keys - * @param derivations - public multisig keys of other participants - * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants - */ - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations); - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations); - /** - * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi) - * @param derivations - others' participants public multisig keys. - * @return vector of current wallet's multisig secret keys - */ - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations); - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys); +namespace cryptonote { struct account_keys; } + +namespace multisig +{ /** - * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys - * @param pkeys unique public multisig keys - * @return multisig wallet's spend public key - */ - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys); - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki); - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold); -} + * @brief get_multisig_blinded_secret_key - converts an input private key into a blinded multisig private key + * Use 1a: converts account private spend key into multisig private key, which is used for key exchange and message signing + * Use 1b: converts account private view key into ancillary private key share, for the composite multisig private view key + * Use 2: converts DH shared secrets (curve points) into private keys, which are intermediate private keys in multisig key exchange + * @param key - private key to transform + * @return transformed private key + */ + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); + + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki); + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R); + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki); +} //namespace multisig diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp new file mode 100644 index 000000000..9bdcf2dbc --- /dev/null +++ b/src/multisig/multisig_account.cpp @@ -0,0 +1,203 @@ +// Copyright (c) 2021-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. + +#include "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstdint> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_pubkey{rct::rct2pk(rct::identity())}, + m_common_pubkey{rct::rct2pk(rct::identity())}, + m_kex_rounds_complete{0}, + m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector<crypto::public_key>{}, base_common_privkey}.get_msg()} + { + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + multisig_keyset_map_memsafe_t kex_origins_map, + std::string next_round_kex_message) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_privkeys{std::move(multisig_privkeys)}, + m_common_privkey{common_privkey}, + m_multisig_pubkey{multisig_pubkey}, + m_common_pubkey{common_pubkey}, + m_kex_rounds_complete{kex_rounds_complete}, + m_kex_keys_to_origins_map{std::move(kex_origins_map)}, + m_next_round_kex_message{std::move(next_round_kex_message)} + { + CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized"); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + set_multisig_config(threshold, std::move(signers)); + + // kex rounds should not exceed post-kex verification round + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)}; + CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete <= kex_rounds_required + 1, + "multisig account: tried to reconstruct account, but kex rounds complete counter is invalid."); + + // once an account is done with kex, the 'next kex msg' is always the post-kex verification message + // i.e. the multisig account pubkey signed by the signer's privkey AND the common pubkey + if (main_kex_rounds_done()) + { + m_next_round_kex_message = multisig_kex_msg{kex_rounds_required + 1, + m_base_privkey, + std::vector<crypto::public_key>{m_multisig_pubkey, m_common_pubkey}}.get_msg(); + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::account_is_active() const + { + return m_kex_rounds_complete > 0; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::main_kex_rounds_done() const + { + if (account_is_active()) + return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold); + else + return false; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::multisig_is_ready() const + { + if (main_kex_rounds_done()) + return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1; + else + return false; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers) + { + // validate + CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold."); + CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS, + "multisig account: tried to set invalid number of signers."); + + for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it) + { + // signer pubkeys must be in main subgroup, and not identity + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())), + "multisig account: tried to set signers, but a signer pubkey is invalid."); + } + + // own pubkey should be in signers list + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(), + "multisig account: tried to set signers, but did not find the account's base pubkey in signer list."); + + // sort signers + std::sort(signers.begin(), signers.end()); + + // signers should all be unique + CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(signers.begin(), signers.end()) == signers.end(), + "multisig account: tried to set signers, but there are duplicate signers unexpectedly."); + + // set + m_threshold = threshold; + m_signers = std::move(signers); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1) + { + CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized"); + + // only mutate account if update succeeds + multisig_account temp_account{*this}; + temp_account.set_multisig_config(threshold, std::move(signers)); + temp_account.kex_update_impl(expanded_msgs_rnd1); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); + CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); + + multisig_account temp_account{*this}; + temp_account.kex_update_impl(expanded_msgs); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) + { + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1"); + return num_signers - threshold + 1; + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h new file mode 100644 index 000000000..7b372bbff --- /dev/null +++ b/src/multisig/multisig_account.h @@ -0,0 +1,248 @@ +// Copyright (c) 2021-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. + +#pragma once + +#include "crypto/crypto.h" +#include "multisig_kex_msg.h" + +#include <cstdint> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + + +namespace multisig +{ + /** + * multisig account: + * + * - handles account keys for an M-of-N multisig participant (M <= N; M >= 1; N >= 2) + * - encapsulates multisig account construction process (via key exchange [kex]) + * - TODO: encapsulates key preparation for aggregation-style signing + * + * :: multisig pubkey: the private key is split, M group participants are required to reassemble (e.g. to sign something) + * - in cryptonote, this is the multisig spend key + * :: multisig common pubkey: the private key is known to all participants (e.g. for authenticating as a group member) + * - in cryptonote, this is the multisig view key + * + * + * multisig key exchange: + * + * An 'M-of-N' (M <= N; M >= 1; N >= 2) multisignature key is a public key where at least 'M' out of 'N' + * possible co-signers must collaborate in order to create a signature. + * + * Constructing a multisig key involves a series of Diffie-Hellman exchanges between participants. + * At the end of key exchange (kex), each participant will hold a number of private keys. Each private + * key is shared by a group of (N - M + 1) participants. This way if (N - M) co-signers are missing, every + * private key will be held by at least one of the remaining M people. + * + * Note on MULTISIG_MAX_SIGNERS: During key exchange, participants will have up to '(N - 1) choose (N - M)' + * key shares. If N is large, then the max number of key shares (when M = (N-1)/2) can be huge. A limit of N <= 16 was + * arbitrarily chosen as a power of 2 that can accomodate the vast majority of practical use-cases. To increase the + * limit, FROST-style key aggregation should be used instead (it is more efficient than DH-based key generation + * when N - M > 1). + * + * - Further reading + * - MRL-0009: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * - MuSig2: https://eprint.iacr.org/2020/1261 + * - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3 + * - FROST: https://eprint.iacr.org/2018/417 + */ + using multisig_keyset_map_memsafe_t = + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>; + + class multisig_account final + { + public: + //constructors + // default constructor + multisig_account() = default; + + /** + * construct from base privkeys + * + * - prepares a kex msg for the first round of multisig key construction. + * - the local account's kex msgs are signed with the base_privkey + * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey + */ + multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey); + + // reconstruct from full account details (not recommended) + multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + multisig_keyset_map_memsafe_t kex_origins_map, + std::string next_round_kex_message); + + // copy constructor: default + + //destructor: default + ~multisig_account() = default; + + //overloaded operators: none + + //getters + // get threshold + std::uint32_t get_threshold() const { return m_threshold; } + // get signers + const std::vector<crypto::public_key>& get_signers() const { return m_signers; } + // get base privkey + const crypto::secret_key& get_base_privkey() const { return m_base_privkey; } + // get base pubkey + const crypto::public_key& get_base_pubkey() const { return m_base_pubkey; } + // get base common privkey + const crypto::secret_key& get_base_common_privkey() const { return m_base_common_privkey; } + // get multisig privkeys + const std::vector<crypto::secret_key>& get_multisig_privkeys() const { return m_multisig_privkeys; } + // get common privkey + const crypto::secret_key& get_common_privkey() const { return m_common_privkey; } + // get multisig pubkey + const crypto::public_key& get_multisig_pubkey() const { return m_multisig_pubkey; } + // get common pubkey + const crypto::public_key& get_common_pubkey() const { return m_common_pubkey; } + // get kex rounds complete + std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; } + // get kex keys to origins map + const multisig_keyset_map_memsafe_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; } + // get the kex msg for the next round + const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; } + + //account status functions + // account has been intialized, and the account holder can use the 'common' key + bool account_is_active() const; + // account has gone through main kex rounds, only remaining step is to verify all other participants are ready + bool main_kex_rounds_done() const; + // account is ready to make multisig signatures + bool multisig_is_ready() const; + + //account helpers + private: + // set the threshold (M) and signers (N) + void set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers); + + //account mutators: key exchange to set up account + public: + /** + * brief: initialize_kex - initialize key exchange + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + */ + void initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1); + /** + * brief: kex_update - Complete the 'in progress' kex round and set the kex message for the next round. + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + * - The main interface for multisig key exchange, this handles all the work of processing input messages, + * creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete. + * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round + */ + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs); + + private: + // implementation of kex_update() (non-transactional) + void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs); + /** + * brief: initialize_kex_update - Helper for kex_update_impl() + * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key + * if appropriate. + * param: expanded_msgs - set of multisig kex messages to process + * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) + * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + */ + void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t kex_rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out); + /** + * brief: finalize_kex_update - Helper for kex_update_impl() + * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) + * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to + * inoutparam: temp_account_inout - account to perform last update steps on + */ + void finalize_kex_update(const std::uint32_t kex_rounds_required, + multisig_keyset_map_memsafe_t result_keys_to_origins_map); + + //member variables + private: + /// misc. account details + // [M] minimum number of co-signers to sign a message with the aggregate pubkey + std::uint32_t m_threshold{0}; + // [N] base keys of all participants in the multisig (used to initiate key exchange, and as participant ids for msg signing) + std::vector<crypto::public_key> m_signers; + + /// local participant's personal keys + // base keypair of the participant + // - used for signing messages, as the initial base key for key exchange, and to make DH derivations for key exchange + crypto::secret_key m_base_privkey; + crypto::public_key m_base_pubkey; + // common base privkey, used to produce the aggregate common privkey + crypto::secret_key m_base_common_privkey; + + /// core multisig account keys + // the account's private key shares of the multisig address + // TODO: also record which other signers have these privkeys, to enable aggregation signing (instead of round-robin) + std::vector<crypto::secret_key> m_multisig_privkeys; + // a privkey owned by all multisig participants (e.g. a cryptonote view key) + crypto::secret_key m_common_privkey; + // the multisig public key (e.g. a cryptonote spend key) + crypto::public_key m_multisig_pubkey; + // the common public key (e.g. a view spend key) + crypto::public_key m_common_pubkey; + + /// kex variables + // number of key exchange rounds that have been completed (all messages for the round collected and processed) + std::uint32_t m_kex_rounds_complete{0}; + // this account's pubkeys for the in-progress key exchange round + // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done) + multisig_keyset_map_memsafe_t m_kex_keys_to_origins_map; + // the account's message for the in-progress key exchange round + std::string m_next_round_kex_message; + }; + + /** + * brief: multisig_kex_rounds_required - The number of key exchange rounds required to produce an M-of-N shared key. + * - Key exchange (kex) is a synchronous series of 'rounds'. In an 'active round', participants send messages + * to each other. + * - A participant considers a round 'complete' when they have collected sufficient messages + * from other participants, processed those messages, and updated their multisig account state. + * - Typically (as implemented in this module), completing a round coincides with making a message for the next round. + * param: num_signers - number of participants in multisig (N) + * param: threshold - threshold of multisig (M) + * return: number of kex rounds required + */ + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); +} //namespace multisig diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp new file mode 100644 index 000000000..be9ed9cb2 --- /dev/null +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -0,0 +1,825 @@ +// Copyright (c) 2021-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. + +#include "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" + +#include <boost/math/special_functions/binomial.hpp> + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <limits> +#include <memory> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: check_multisig_config - validate multisig configuration details + * param: round - the round of the message that should be produced + * param: threshold - threshold for multisig (M in M-of-N) + * param: num_signers - number of participants in multisig (N) + */ + //---------------------------------------------------------------------------------------------------------------------- + static void check_multisig_config(const std::uint32_t round, + const std::uint32_t threshold, + const std::uint32_t num_signers) + { + CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, + "Multisig threshold may not be larger than number of signers."); + CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); + CHECK_AND_ASSERT_THROW_MES(round > 0, "Multisig kex round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold) + 1, + "Trying to process multisig kex for an invalid round."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key + * Converts an input public key into a crypto private key (type cast, does not change serialization), + * then passes it to get_multisig_blinded_secret_key(). + * + * Result: + * - privkey = H(derivation) + * - pubkey = privkey * G + * param: derivation - a curve point + * outparam: derived_pubkey_out - public key of the resulting privkey + * return: multisig private key + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::secret_key calculate_multisig_keypair_from_derivation(const crypto::public_key_memsafe &derivation, + crypto::public_key &derived_pubkey_out) + { + crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(derivation))); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(blinded_skey, derived_pubkey_out), "Failed to derive public key"); + + return blinded_skey; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: make_multisig_common_privkey - Create the 'common' multisig privkey, owned by all multisig participants. + * - common privkey = H(sorted base common privkeys) + * param: participant_base_common_privkeys - Base common privkeys contributed by multisig participants. + * outparam: common_privkey_out - result + */ + //---------------------------------------------------------------------------------------------------------------------- + static void make_multisig_common_privkey(std::vector<crypto::secret_key> participant_base_common_privkeys, + crypto::secret_key &common_privkey_out) + { + // sort the privkeys for consistency + //TODO: need a constant-time operator< for sorting secret keys + std::sort(participant_base_common_privkeys.begin(), participant_base_common_privkeys.end(), + [](const crypto::secret_key &key1, const crypto::secret_key &key2) -> bool + { + return memcmp(&key1, &key2, sizeof(crypto::secret_key)) < 0; + } + ); + + // privkey = H(sorted ancillary base privkeys) + crypto::hash_to_scalar(participant_base_common_privkeys.data(), + participant_base_common_privkeys.size()*sizeof(crypto::secret_key), + common_privkey_out); + + CHECK_AND_ASSERT_THROW_MES(common_privkey_out != crypto::null_skey, "Unexpected null secret key (danger!)."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: compute_multisig_aggregation_coefficient - creates aggregation coefficient for a specific public key in a set + * of public keys + * + * WARNING: The coefficient will only be deterministic if... + * 1) input keys are pre-sorted + * - tested here + * 2) input keys are in canonical form (compressed points in the prime-order subgroup of Ed25519) + * - untested here for performance + * param: sorted_keys - set of component public keys that will be merged into a multisig public spend key + * param: aggregation_key - one of the component public keys + * return: aggregation coefficient + */ + //---------------------------------------------------------------------------------------------------------------------- + static rct::key compute_multisig_aggregation_coefficient(const std::vector<crypto::public_key> &sorted_keys, + const crypto::public_key &aggregation_key) + { + CHECK_AND_ASSERT_THROW_MES(std::is_sorted(sorted_keys.begin(), sorted_keys.end()), + "Keys for aggregation coefficient aren't sorted."); + + // aggregation key must be in sorted_keys + CHECK_AND_ASSERT_THROW_MES(std::find(sorted_keys.begin(), sorted_keys.end(), aggregation_key) != sorted_keys.end(), + "Aggregation key expected to be in input keyset."); + + // aggregation coefficient salt + rct::key salt = rct::zero(); + static_assert(sizeof(rct::key) >= sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION), "Hash domain separator is too big."); + memcpy(salt.bytes, config::HASH_KEY_MULTISIG_KEY_AGGREGATION, sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION)); + + // coeff = H(aggregation_key, sorted_keys, domain-sep) + rct::keyV data; + data.reserve(sorted_keys.size() + 2); + data.push_back(rct::pk2rct(aggregation_key)); + for (const auto &key : sorted_keys) + data.push_back(rct::pk2rct(key)); + data.push_back(salt); + + // note: coefficient is considered public knowledge, no need to memwipe data + return rct::hash_to_scalar(data); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: generate_multisig_aggregate_key - generates a multisig public spend key via key aggregation + * Key aggregation via aggregation coefficients prevents key cancellation attacks. + * See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * param: final_keys - address components (public keys) obtained from other participants (not shared with local) + * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference) + * return: final multisig public spend key for the account + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::public_key generate_multisig_aggregate_key(std::vector<crypto::public_key> final_keys, + std::vector<crypto::secret_key> &privkeys_inout) + { + // collect all public keys that will go into the spend key (these don't need to be memsafe) + final_keys.reserve(final_keys.size() + privkeys_inout.size()); + + // 1. convert local multisig private keys to pub keys + // 2. insert to final keyset if not there yet + // 3. save the corresponding index of input priv key set for later reference + std::unordered_map<crypto::public_key, std::size_t> own_keys_mapping; + + for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index) + { + crypto::public_key pubkey; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key"); + + own_keys_mapping[pubkey] = multisig_keys_index; + + final_keys.push_back(pubkey); + } + + // sort input final keys for computing aggregation coefficients (lowest to highest) + // note: input should be sanitized (no duplicates) + std::sort(final_keys.begin(), final_keys.end()); + CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(final_keys.begin(), final_keys.end()) == final_keys.end(), + "Unexpected duplicate found in input list."); + + // key aggregation + rct::key aggregate_key = rct::identity(); + + for (const crypto::public_key &key : final_keys) + { + // get aggregation coefficient + rct::key coeff = compute_multisig_aggregation_coefficient(final_keys, key); + + // convert private key if possible + // note: retain original priv key index in input list, in case order matters upstream + auto found_key = own_keys_mapping.find(key); + if (found_key != own_keys_mapping.end()) + { + // k_agg = coeff*k_base + sc_mul((unsigned char*)&(privkeys_inout[found_key->second]), + coeff.bytes, + (const unsigned char*)&(privkeys_inout[found_key->second])); + + CHECK_AND_ASSERT_THROW_MES(privkeys_inout[found_key->second] != crypto::null_skey, + "Multisig privkey with aggregation coefficient unexpectedly null."); + } + + // convert public key (pre-merge operation) + // K_agg = coeff*K_base + rct::key converted_pubkey = rct::scalarmultKey(rct::pk2rct(key), coeff); + + // build aggregate key (merge operation) + rct::addKeys(aggregate_key, aggregate_key, converted_pubkey); + } + + return rct::rct2pk(aggregate_key); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_make_round_keys - Makes a kex round's keys. + * - Involves DH exchanges with pubkeys provided by other participants. + * - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you]. + * param: base_privkey - account's base private key, for performing DH exchanges and signing messages + * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of + * participants who will share each derivation with you + * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys + */ + //---------------------------------------------------------------------------------------------------------------------- + static void multisig_kex_make_round_keys(const crypto::secret_key &base_privkey, + multisig_keyset_map_memsafe_t pubkey_origins_map, + multisig_keyset_map_memsafe_t &derivation_origins_map_out) + { + // make shared secrets with input pubkeys + derivation_origins_map_out.clear(); + + for (auto &pubkey_and_origins : pubkey_origins_map) + { + // D = 8 * k_base * K_pubkey + // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local + // base_privkey is a multiple of 8 or not + // note2: avoid making temporaries that won't be memwiped + rct::key derivation_rct; + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(&derivation_rct, sizeof(rct::key)); + }); + + rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey)); + rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT); + + // retain mapping between pubkey's origins and the DH derivation + // note: if working on last kex round, then caller must know how to handle these derivations properly + derivation_origins_map_out[rct::rct2pk(derivation_rct)] = std::move(pubkey_and_origins.second); + } + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: check_messages_round - Check that a set of messages have an expected round number. + * param: expanded_msgs - set of multisig kex messages to process + * param: expected_round - round number the kex messages should have + */ + //---------------------------------------------------------------------------------------------------------------------- + static void check_messages_round(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t expected_round) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + const std::uint32_t round{expanded_msgs[0].get_round()}; + CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Messages don't have the expected kex round number."); + + for (const auto &expanded_msg : expanded_msgs) + CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages. + * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key, + * ignores messages signed by the local account, ignores keys found in input 'exclusion set', + * constructs map of pubkey:origins. + * - Requires that all input msgs have the same round number. + * + * origins = all the signing pubkeys that recommended a given pubkey found in input msgs + * + * - If the messages' round numbers are all '1', then only the message signing pubkey is considered + * 'recommended'. Furthermore, the 'exclusion set' is ignored. + * param: own_pubkey - local account's signing key (key used to sign multisig messages) + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - pubkeys to exclude from output set + * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins + * return: round number shared by all input msgs + */ + //---------------------------------------------------------------------------------------------------------------------- + static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + multisig_keyset_map_memsafe_t &sanitized_pubkeys_out) + { + // all messages should have the same round (redundant sanity check) + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + const std::uint32_t round{expanded_msgs[0].get_round()}; + check_messages_round(expanded_msgs, round); + + sanitized_pubkeys_out.clear(); + + // get all pubkeys from input messages, add them to pubkey:origins map + // - origins = all the signing pubkeys that recommended a given msg pubkey + for (const auto &expanded_msg : expanded_msgs) + { + // ignore messages from self + if (expanded_msg.get_signing_pubkey() == own_pubkey) + continue; + + // in round 1, only the signing pubkey is treated as a msg pubkey + if (round == 1) + { + // note: ignores duplicates + sanitized_pubkeys_out[expanded_msg.get_signing_pubkey()].insert(expanded_msg.get_signing_pubkey()); + } + // in other rounds, only the msg pubkeys are treated as msg pubkeys + else + { + // copy all pubkeys from message into list + for (const auto &pubkey : expanded_msg.get_msg_pubkeys()) + { + // ignore own pubkey + if (pubkey == own_pubkey) + continue; + + // ignore pubkeys in 'ignore' set + if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end()) + continue; + + // note: ignores duplicates + sanitized_pubkeys_out[pubkey].insert(expanded_msg.get_signing_pubkey()); + } + } + } + + return round; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round. + * - Sanitizes input msgs. + * - Require uniqueness in: 'exclude_pubkeys'. + * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers. + * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be + * shared by (N - M + 1) signers. + * - Requires that msgs are signed by only keys in 'signers'. + * - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations each signer recommends for round 'expected_round', excluding derivations shared + * with the local account. + * - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations the local account has corresponding to round 'expected_round'. + * param: base_pubkey - multisig account's base public key + * param: expected_round - expected kex round of input messages + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round' + * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_keyset_map_memsafe_t evaluate_multisig_kex_round_msgs( + const crypto::public_key &base_pubkey, + const std::uint32_t expected_round, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys) + { + // exclude_pubkeys should all be unique + for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) + { + CHECK_AND_ASSERT_THROW_MES(std::find(exclude_pubkeys.begin(), it, *it) == it, + "Found duplicate pubkeys for exclusion unexpectedly."); + } + + // sanitize input messages + multisig_keyset_map_memsafe_t pubkey_origins_map; + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map); + CHECK_AND_ASSERT_THROW_MES(round == expected_round, + "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + + // evaluate pubkeys collected + std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; + + // 1. each pubkey should be recommended by a precise number of signers + for (const auto &pubkey_and_origins : pubkey_origins_map) + { + // expected amount = round_num + // With each successive round, pubkeys are shared by incrementally larger groups, + // starting at 1 in round 1 (i.e. the local multisig key to start kex with). + CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // map (sanitized) pubkeys back to origins + for (const auto &origin : pubkey_and_origins.second) + origin_pubkeys_map[origin].insert(pubkey_and_origins.first); + } + + // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer) + CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1, + "Number of unique other signers does not equal number of other signers that recommended pubkeys."); + + // 3. each origin should recommend a precise number of pubkeys + + // TODO: move to a 'math' library, with unit tests + auto n_choose_k_f = + [](const std::uint32_t n, const std::uint32_t k) -> std::uint32_t + { + static_assert(std::numeric_limits<std::int32_t>::digits <= std::numeric_limits<double>::digits, + "n_choose_k requires no rounding issues when converting between int32 <-> double."); + + if (n < k) + return 0; + + double fp_result = boost::math::binomial_coefficient<double>(n, k); + + if (fp_result < 0) + return 0; + + if (fp_result > std::numeric_limits<std::int32_t>::max()) // note: std::round() returns std::int32_t + return 0; + + return static_cast<std::uint32_t>(std::round(fp_result)); + }; + + // other signers: (N - 2) choose (msg_round_num - 1) + // - Each signer recommends keys they share with other signers. + // - In each round, a signer shares a key with 'round num - 1' other signers. + // - Since 'origins pubkey map' excludes keys shared with the local account, + // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers). + // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). + // - Each origin should have a shared key with each group of size 'round - 1'. + // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may + // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). + const std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); + + // local: (N - 1) choose (msg_round_num - 1) + const std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1); + + // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case + CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, + "Bad num signers or round num (possibly numerical limits exceeded)."); + + // check that local account recommends expected number of keys + CHECK_AND_ASSERT_THROW_MES(exclude_pubkeys.size() == expected_recommendations_self, + "Local account did not recommend expected number of multisig keys."); + + // check that other signers recommend expected number of keys + for (const auto &origin_and_pubkeys : origin_pubkeys_map) + { + CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // 2 (continued). only expected signers should be recommending keys + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(), + "Multisig kex message with unexpected signer encountered."); + } + + // note: above tests implicitly detect if the total number of recommended keys is correct or not + return pubkey_origins_map; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: evaluate_multisig_post_kex_round_msgs - Evaluate messages for the post-kex verification round. + * - Sanitizes input msgs. + * - Requires that only one pubkey is recommended. + * - Requires that all signers (other than self) recommend that one pubkey. + * param: base_pubkey - multisig account's base public key + * param: expected_round - expected kex round of input messages + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * return: sanitized and validated pubkey:origins map + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_keyset_map_memsafe_t evaluate_multisig_post_kex_round_msgs( + const crypto::public_key &base_pubkey, + const std::uint32_t expected_round, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs) + { + // sanitize input messages + const std::vector<crypto::public_key> dummy; + multisig_keyset_map_memsafe_t pubkey_origins_map; + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, dummy, pubkey_origins_map); + CHECK_AND_ASSERT_THROW_MES(round == expected_round, + "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + + // evaluate pubkeys collected + + // 1) there should only be two pubkeys + CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.size() == 2, + "Multisig post-kex round messages from other signers did not all contain two pubkeys."); + + // 2) both keys should be recommended by the same set of signers + CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.begin()->second == (++(pubkey_origins_map.begin()))->second, + "Multisig post-kex round messages from other signers did not all recommend the same pubkey pair."); + + // 3) all signers should be present in the recommendation list + auto origins = pubkey_origins_map.begin()->second; + origins.insert(base_pubkey); //add self + + CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(), + "Multisig post-kex round message origins don't line up with multisig signer set."); + + for (const crypto::public_key &signer : signers) + { + CHECK_AND_ASSERT_THROW_MES(origins.find(signer) != origins.end(), + "Could not find an expected signer in multisig post-kex round messages (all signers expected)."); + } + + return pubkey_origins_map; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_process_round_msgs - Process kex messages for the active kex round. + * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg(). + * - In other words, evaluate the input messages and try to make a message for the next round. + * - Note: Must be called on the final round's msgs to evaluate the final key components + * recommended by other participants. + * param: base_privkey - multisig account's base private key + * param: current_round - round of kex the input messages should be designed for + * param: threshold - threshold for multisig (M in M-of-N) + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + * outparam: keys_to_origins_map_out - map between round keys and identity keys + * - If in the final round, these are key shares recommended by other signers for the final aggregate key. + * - Otherwise, these are the local account's DH derivations for the next round. + * - See multisig_kex_make_next_msg() for an explanation. + * return: multisig kex message for next round, or empty message if 'current_round' is the final round + */ + //---------------------------------------------------------------------------------------------------------------------- + static void multisig_kex_process_round_msgs(const crypto::secret_key &base_privkey, + const crypto::public_key &base_pubkey, + const std::uint32_t current_round, + const std::uint32_t threshold, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + multisig_keyset_map_memsafe_t &keys_to_origins_map_out) + { + check_multisig_config(current_round, threshold, signers.size()); + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(signers.size(), threshold)}; + + // process messages into a [pubkey : {origins}] map + multisig_keyset_map_memsafe_t evaluated_pubkeys; + + if (threshold == 1 && current_round == kex_rounds_required) + { + // in the last main kex round of 1-of-N, all signers share a key so the local signer doesn't care about evaluating + // recommendations from other signers + } + else if (current_round <= kex_rounds_required) + { + // for normal kex rounds, fully evaluate kex round messages + evaluated_pubkeys = evaluate_multisig_kex_round_msgs(base_pubkey, + current_round, + signers, + expanded_msgs, + exclude_pubkeys); + } + else //(current_round == kex_rounds_required + 1) + { + // for the post-kex verification round, validate the last kex round's messages + evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey, + current_round, + signers, + expanded_msgs); + } + + // prepare keys-to-origins map for updating the multisig account + if (current_round < kex_rounds_required) + { + // normal kex round: make new keys + multisig_kex_make_round_keys(base_privkey, std::move(evaluated_pubkeys), keys_to_origins_map_out); + } + else if (current_round >= kex_rounds_required) + { + // last kex round: collect the key shares recommended by other signers for the final aggregate key + // post-kex verification round: save the keys found in input messages + keys_to_origins_map_out = std::move(evaluated_pubkeys); + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t kex_rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out) + { + if (m_kex_rounds_complete == 0) + { + // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys + + // collect participants' base common privkey shares + // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers + // will be blocked by duplicate-signer errors after this function is called + std::vector<crypto::secret_key> participant_base_common_privkeys; + participant_base_common_privkeys.reserve(expanded_msgs.size() + 1); + + // add local ancillary base privkey + participant_base_common_privkeys.emplace_back(m_base_common_privkey); + + // add other signers' base common privkeys + for (const auto &expanded_msg : expanded_msgs) + { + if (expanded_msg.get_signing_pubkey() != m_base_pubkey) + { + participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey()); + } + } + + // make common privkey + make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); + + // set common pubkey + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey), + "Failed to derive public key"); + + // if N-of-N, then the base privkey will be used directly to make the account's share of the final key + if (kex_rounds_required == 1) + { + m_multisig_privkeys.clear(); + m_multisig_privkeys.emplace_back(m_base_privkey); + } + + // exclude all keys the local account recommends + // - in the first round, only the local pubkey is recommended by the local signer + exclude_pubkeys_out.emplace_back(m_base_pubkey); + } + else + { + // in other rounds, kex msgs will contain participants' shared keys + + // ignore shared keys the account helped create for this round + for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map) + { + exclude_pubkeys_out.emplace_back(shared_key_with_origins.first); + } + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::finalize_kex_update(const std::uint32_t kex_rounds_required, + multisig_keyset_map_memsafe_t result_keys_to_origins_map) + { + std::vector<crypto::public_key> next_msg_keys; + + // prepare for next round (or complete the multisig account fully) + if (m_kex_rounds_complete == kex_rounds_required) + { + // post-kex verification round: check that the multisig pubkey and common pubkey were recommended by other signers + CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_multisig_pubkey) > 0, + "Multisig post-kex round: expected multisig pubkey wasn't found in other signers' messages."); + CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_common_pubkey) > 0, + "Multisig post-kex round: expected common pubkey wasn't found in other signers' messages."); + + // save keys that should be recommended to other signers + // - for convenience, re-recommend the post-kex verification message once an account is complete + next_msg_keys.reserve(2); + next_msg_keys.push_back(m_multisig_pubkey); + next_msg_keys.push_back(m_common_pubkey); + } + else if (m_kex_rounds_complete + 1 == kex_rounds_required) + { + // finished with main kex rounds (have set of msgs to complete address) + + // when 'completing the final round', result keys are other signers' shares of the final key + std::vector<crypto::public_key> result_keys; + result_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &result_key_and_origins : result_keys_to_origins_map) + { + result_keys.emplace_back(result_key_and_origins.first); + } + + // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied + m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys); + + // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys) + // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing + m_kex_keys_to_origins_map.clear(); + + // save keys that should be recommended to other signers + // - for post-kex verification, recommend the multisig pubkeys to notify other signers that the local signer is done + next_msg_keys.reserve(2); + next_msg_keys.push_back(m_multisig_pubkey); + next_msg_keys.push_back(m_common_pubkey); + } + else if (m_kex_rounds_complete + 2 == kex_rounds_required) + { + // one more round (must send/receive one more set of kex msgs) + // - at this point, have local signer's pre-aggregation private key shares of the final address + + // result keys are the local signer's DH derivations for the next round + + // derivations are shared secrets between each group of N - M + 1 signers of which the local account is a member + // - convert them to private keys: multisig_key = H(derivation) + // - note: shared key = multisig_key[i]*G is recorded in the kex msg for sending to other participants + // instead of the original 'derivation' value (which MUST be kept secret!) + m_multisig_privkeys.clear(); + m_multisig_privkeys.reserve(result_keys_to_origins_map.size()); + + m_kex_keys_to_origins_map.clear(); + next_msg_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &derivation_and_origins : result_keys_to_origins_map) + { + // multisig_privkey = H(derivation) + // derived pubkey = multisig_key * G + crypto::public_key_memsafe derived_pubkey; + m_multisig_privkeys.push_back( + calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)); + + // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key] + m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second); + + // save keys that should be recommended to other signers + // - The keys multisig_key*G are sent to other participants in the message, so they can be used to produce the final + // multisig key via generate_multisig_spend_public_key(). + next_msg_keys.push_back(derived_pubkey); + } + } + else //(m_kex_rounds_complete + 3 <= kex_rounds_required) + { + // next round is an 'intermediate' key exchange round, so there is nothing special to do here + + // save keys that should be recommended to other signers + // - Send this round's DH derivations to other participants, who will make more DH derivations for the following round. + next_msg_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &derivation_and_origins : result_keys_to_origins_map) + next_msg_keys.push_back(derivation_and_origins.first); + + // save the account's kex keys for this round [DH derivation : other signers who should have the same derivation] + m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map); + } + + // a full set of msgs has been collected and processed, so the 'round is complete' + ++m_kex_rounds_complete; + + // make next round's message (or reproduce the post-kex verification round if kex is complete) + m_next_round_kex_message = multisig_kex_msg{ + (m_kex_rounds_complete > kex_rounds_required ? kex_rounds_required : m_kex_rounds_complete) + 1, + m_base_privkey, + std::move(next_msg_keys)}.get_msg(); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) + { + // check messages are for the expected kex round + check_messages_round(expanded_msgs, m_kex_rounds_complete + 1); + + // check kex round count + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)}; + + CHECK_AND_ASSERT_THROW_MES(kex_rounds_required > 0, "Multisig kex rounds required unexpectedly 0."); + CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete < kex_rounds_required + 1, + "Multisig kex has already completed all required rounds (including post-kex verification)."); + + // initialize account update + std::vector<crypto::public_key> exclude_pubkeys; + initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys); + + // process messages into a [pubkey : {origins}] map + multisig_keyset_map_memsafe_t result_keys_to_origins_map; + multisig_kex_process_round_msgs( + m_base_privkey, + m_base_pubkey, + m_kex_rounds_complete + 1, + m_threshold, + m_signers, + expanded_msgs, + exclude_pubkeys, + result_keys_to_origins_map); + + // finish account update + finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map)); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp new file mode 100644 index 000000000..e3417b896 --- /dev/null +++ b/src/multisig/multisig_clsag_context.cpp @@ -0,0 +1,257 @@ +// 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. + +#include "multisig_clsag_context.h" + +#include "int-util.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstring> +#include <string> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +template<std::size_t N> +static rct::key string_to_key(const unsigned char (&str)[N]) { + rct::key tmp{}; + static_assert(sizeof(tmp.bytes) >= N, ""); + std::memcpy(tmp.bytes, str, N); + return tmp; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void encode_int_to_key_le(const unsigned int i, rct::key &k_out) +{ + static_assert(sizeof(unsigned int) <= sizeof(std::uint64_t), "unsigned int max too large"); + static_assert(sizeof(std::uint64_t) <= sizeof(rct::key), ""); + std::uint64_t temp_i{SWAP64LE(i)}; + std::memcpy(k_out.bytes, &temp_i, sizeof(temp_i)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components +) +{ + initialized = false; + + n = P.size(); + if (n <= 0) + return false; + if (C_nonzero.size() != n) + return false; + if (s.size() != n) + return false; + if (l >= n) + return false; + + c_params.clear(); + c_params.reserve(n * 2 + 5); + b_params.clear(); + b_params.reserve(n * 3 + 2 * num_alpha_components + 7); + + c_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND)); + b_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND_MULTISIG)); + c_params.insert(c_params.end(), P.begin(), P.end()); + b_params.insert(b_params.end(), P.begin(), P.end()); + c_params.insert(c_params.end(), C_nonzero.begin(), C_nonzero.end()); + b_params.insert(b_params.end(), C_nonzero.begin(), C_nonzero.end()); + c_params.emplace_back(C_offset); + b_params.emplace_back(C_offset); + c_params.emplace_back(message); + b_params.emplace_back(message); + c_params_L_offset = c_params.size(); + b_params_L_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where L will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for L will be inserted here later + c_params_R_offset = c_params.size(); + b_params_R_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where R will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for R will be inserted here later + b_params.emplace_back(I); + b_params.emplace_back(D); + b_params.insert(b_params.end(), s.begin(), s.begin() + l); //fake responses before 'l' + b_params.insert(b_params.end(), s.begin() + l + 1, s.end()); //fake responses after 'l' + b_params.emplace_back(); + encode_int_to_key_le(l, b_params.back()); //real signing index 'l' + b_params.emplace_back(); + encode_int_to_key_le(num_alpha_components, b_params.back()); //number of parallel nonces + b_params.emplace_back(); + encode_int_to_key_le(n, b_params.back()); //number of ring members + + rct::keyV mu_P_params; + rct::keyV mu_C_params; + mu_P_params.reserve(n * 2 + 4); + mu_C_params.reserve(n * 2 + 4); + + mu_P_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_0)); + mu_C_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_1)); + mu_P_params.insert(mu_P_params.end(), P.begin(), P.end()); + mu_C_params.insert(mu_C_params.end(), P.begin(), P.end()); + mu_P_params.insert(mu_P_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_C_params.insert(mu_C_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_P_params.emplace_back(I); + mu_C_params.emplace_back(I); + mu_P_params.emplace_back(scalarmultKey(D, rct::INV_EIGHT)); + mu_C_params.emplace_back(mu_P_params.back()); + mu_P_params.emplace_back(C_offset); + mu_C_params.emplace_back(C_offset); + mu_P = hash_to_scalar(mu_P_params); + mu_C = hash_to_scalar(mu_C_params); + + rct::geDsmp I_precomp; + rct::geDsmp D_precomp; + rct::precomp(I_precomp.k, I); + rct::precomp(D_precomp.k, D); + rct::key wH_l; + rct::addKeys3(wH_l, mu_P, I_precomp.k, mu_C, D_precomp.k); + rct::precomp(wH_l_precomp.k, wH_l); + W_precomp.resize(n); + H_precomp.resize(n); + for (std::size_t i = 0; i < n; ++i) { + rct::geDsmp P_precomp; + rct::geDsmp C_precomp; + rct::key C; + rct::subKeys(C, C_nonzero[i], C_offset); + rct::precomp(P_precomp.k, P[i]); + rct::precomp(C_precomp.k, C); + rct::key W; + rct::addKeys3(W, mu_P, P_precomp.k, mu_C, C_precomp.k); + rct::precomp(W_precomp[i].k, W); + ge_p3 Hi_p3; + rct::hash_to_p3(Hi_p3, P[i]); + ge_dsm_precomp(H_precomp[i].k, &Hi_p3); + } + rct::precomp(G_precomp.k, rct::G); + this->l = l; + this->s = s; + this->num_alpha_components = num_alpha_components; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::combine_alpha_and_compute_challenge( + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& alpha_combined, + rct::key& c_0, + rct::key& c +) +{ + if (not initialized) + return false; + + if (num_alpha_components != total_alpha_G.size()) + return false; + if (num_alpha_components != total_alpha_H.size()) + return false; + if (num_alpha_components != alpha.size()) + return false; + + // insert aggregate public nonces for L and R components + for (std::size_t i = 0; i < num_alpha_components; ++i) { + b_params[b_params_L_offset + i] = total_alpha_G[i]; + b_params[b_params_R_offset + i] = total_alpha_H[i]; + } + + // musig2-style combination factor 'b' + const rct::key b = rct::hash_to_scalar(b_params); + + // 1) store combined public nonces in the 'L' and 'R' slots for computing the initial challenge + // - L = sum_i(b^i total_alpha_G[i]) + // - R = sum_i(b^i total_alpha_H[i]) + // 2) compute the local signer's combined private nonce + // - alpha_combined = sum_i(b^i * alpha[i]) + rct::key& L_l = c_params[c_params_L_offset]; + rct::key& R_l = c_params[c_params_R_offset]; + rct::key b_i = rct::identity(); + L_l = rct::identity(); + R_l = rct::identity(); + alpha_combined = rct::zero(); + for (std::size_t i = 0; i < num_alpha_components; ++i) { + rct::addKeys(L_l, L_l, rct::scalarmultKey(total_alpha_G[i], b_i)); + rct::addKeys(R_l, R_l, rct::scalarmultKey(total_alpha_H[i], b_i)); + sc_muladd(alpha_combined.bytes, alpha[i].bytes, b_i.bytes, alpha_combined.bytes); + sc_mul(b_i.bytes, b_i.bytes, b.bytes); + } + + // compute initial challenge from real spend components + c = rct::hash_to_scalar(c_params); + + // 1) c_0: find the CLSAG's challenge for index '0', which will be stored in the proof + // note: in the CLSAG implementation in ringct/rctSigs, c_0 is denoted 'c1' (a notation error) + // 2) c: find the final challenge for the multisig signers to respond to + for (std::size_t i = (l + 1) % n; i != l; i = (i + 1) % n) { + if (i == 0) + c_0 = c; + rct::addKeys3(c_params[c_params_L_offset], s[i], G_precomp.k, c, W_precomp[i].k); + rct::addKeys3(c_params[c_params_R_offset], s[i], H_precomp[i].k, c, wH_l_precomp.k); + c = rct::hash_to_scalar(c_params); + } + if (l == 0) + c_0 = c; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::get_mu( + rct::key& mu_P, + rct::key& mu_C +) const +{ + if (not initialized) + return false; + mu_P = this->mu_P; + mu_C = this->mu_C; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h new file mode 100644 index 000000000..5017e8688 --- /dev/null +++ b/src/multisig/multisig_clsag_context.h @@ -0,0 +1,137 @@ +// 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. + +//// +// References +// - CLSAG (base signature scheme): https://eprint.iacr.org/2019/654 +// - MuSig2 (style for multisig signing): https://eprint.iacr.org/2020/1261 +/// + + +#pragma once + +#include "ringct/rctTypes.h" + +#include <vector> + + +namespace multisig { + +namespace signing { + +class CLSAG_context_t final { +private: + // is the CLSAG context initialized? + bool initialized; + // challenge components: c = H(domain-separator, {P}, {C}, C_offset, message, L, R) + rct::keyV c_params; + // indices in c_params where L and R will be + std::size_t c_params_L_offset; + std::size_t c_params_R_offset; + // musig2-style nonce combination factor components for multisig signing + // b = H(domain-separator, {P}, {C}, C_offset, message, {L_combined_alphas}, {R_combined_alphas}, I, D, {s_non_l}, l, k, n) + // - {P} = ring of one-time addresses + // - {C} = ring of amount commitments (1:1 with one-time addresses) + // - C_offset = pseudo-output commitment to offset all amount commitments with + // - message = message the CLSAG will sign + // - {L_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's L component + // - {R_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's R component + // - I = key image for one-time address at {P}[l] + // - D = auxiliary key image for the offsetted amount commitment '{C}[l] - C_offset' + // - {s_non_l} = fake responses for this proof + // - l = real signing index in {P} and '{C} - C_offset' + // - k = number of parallel nonces that each participant provides + // - n = number of ring members + rct::keyV b_params; + // indices in b_params where L and R 'alpha' components will be + std::size_t b_params_L_offset; + std::size_t b_params_R_offset; + // CLSAG 'concise' coefficients for {P} and '{C} - C_offset' + // mu_x = H(domain-separator, {P}, {C}, I, (1/8)*D, C_offset) + // - note: 'D' is stored in the form '(1/8)*D' in transaction data + rct::key mu_P; + rct::key mu_C; + // ring size + std::size_t n; + // aggregate key image: mu_P*I + mu_C*D + rct::geDsmp wH_l_precomp; + // aggregate ring members: mu_P*P_i + mu_C*(C_i - C_offset) + std::vector<rct::geDsmp> W_precomp; + // key image component base keys: H_p(P_i) + std::vector<rct::geDsmp> H_precomp; + // cache for later: generator 'G' in 'precomp' representation + rct::geDsmp G_precomp; + // real signing index in this CLSAG + std::size_t l; + // signature responses + rct::keyV s; + // number of signing nonces expected per signer + std::size_t num_alpha_components; +public: + CLSAG_context_t() : initialized{false} {} + + // prepare CLSAG challenge context + bool init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components + ); + + // get the local signer's combined musig2-style private nonce and compute the CLSAG challenge + bool combine_alpha_and_compute_challenge( + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's L component + const rct::keyV& total_alpha_G, + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's R component + const rct::keyV& total_alpha_H, + // local signer's private musig2-style nonces + const rct::keyV& alpha, + // local signer's final private nonce, using musig2-style combination with factor 'b' + // alpha_combined = sum_i(b^i * alpha[i]) + rct::key& alpha_combined, + // CLSAG challenge to store in the proof + rct::key& c_0, + // final CLSAG challenge to respond to (need this to make multisig partial signatures) + rct::key& c + ); + + // getter for CLSAG 'concise' coefficients + bool get_mu( + rct::key& mu_P, + rct::key& mu_C + ) const; +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp new file mode 100644 index 000000000..c717e23ad --- /dev/null +++ b/src/multisig/multisig_kex_msg.cpp @@ -0,0 +1,290 @@ +// Copyright (c) 2021-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. + +#include "multisig_kex_msg.h" +#include "multisig_kex_msg_serialization.h" + +#include "common/base58.h" +#include "crypto/crypto.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "include_base_utils.h" +#include "ringct/rctOps.h" +#include "serialization/binary_archive.h" +#include "serialization/serialization.h" + +#include <boost/utility/string_ref.hpp> + +#include <sstream> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +const boost::string_ref MULTISIG_KEX_V1_MAGIC{"MultisigV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V1_MAGIC{"MultisigxV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_1{"MultisigxV2R1"}; //round 1 +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_N{"MultisigxV2Rn"}; //round n > 1 + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey) : + m_kex_round{round} + { + CHECK_AND_ASSERT_THROW_MES(round > 0, "Kex round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&signing_privkey) == 0 && + signing_privkey != crypto::null_skey, "Invalid msg signing key."); + + if (round == 1) + { + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&msg_privkey) == 0 && + msg_privkey != crypto::null_skey, "Invalid msg privkey."); + + m_msg_privkey = msg_privkey; + } + else + { + for (const auto &pubkey : msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey for message was invalid."); + CHECK_AND_ASSERT_THROW_MES((rct::scalarmultKey(rct::pk2rct(pubkey), rct::curveOrder()) == rct::identity()), + "Pubkey for message was not in prime subgroup."); + } + + m_msg_pubkeys = std::move(msg_pubkeys); + } + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signing_privkey, m_signing_pubkey), + "Failed to derive public key"); + + // sets message and signing pub key + construct_msg(signing_privkey); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(std::string msg) : m_msg{std::move(msg)} + { + parse_and_validate_msg(); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + crypto::hash multisig_kex_msg::get_msg_to_sign() const + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + /// + + std::string data; + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + data.reserve(MULTISIG_KEX_MSG_V2_MAGIC_1.size() + 4 + 32*(1 + (m_kex_round == 1 ? 1 : 0) + m_msg_pubkeys.size())); + + // versioning domain-sep + if (m_kex_round == 1) + data.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + else + data.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + // kex_round as little-endian bytes + for (std::size_t i{0}; i < 4; ++i) + { + data += static_cast<char>(m_kex_round >> i*8); + } + + // signing pubkey + data.append((const char *)&m_signing_pubkey, sizeof(crypto::public_key)); + + // add msg privkey if kex_round == 1 + if (m_kex_round == 1) + data.append((const char *)&m_msg_privkey, sizeof(crypto::secret_key)); + else + { + // only add pubkeys if not round 1 + + // msg pubkeys + for (const auto &key : m_msg_pubkeys) + data.append((const char *)&key, sizeof(crypto::public_key)); + } + + // message to sign + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + + return hash; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::construct_msg(const crypto::secret_key &signing_privkey) + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | serialize(msg_content | crypto_sig[signing_privkey](sign_msg)) + /// + + // sign the message + crypto::signature msg_signature; + crypto::hash msg_to_sign{get_msg_to_sign()}; + crypto::generate_signature(msg_to_sign, m_signing_pubkey, signing_privkey, msg_signature); + + // assemble the message + m_msg.clear(); + + std::stringstream serialized_msg_ss; + binary_archive<true> b_archive(serialized_msg_ss); + + if (m_kex_round == 1) + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + + multisig_kex_msg_serializable_round1 msg_serializable; + msg_serializable.msg_privkey = m_msg_privkey; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + else + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + multisig_kex_msg_serializable_general msg_serializable; + msg_serializable.kex_round = m_kex_round; + msg_serializable.msg_pubkeys = m_msg_pubkeys; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + + m_msg.append(tools::base58::encode(serialized_msg_ss.str())); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::parse_and_validate_msg() + { + // check message type + CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty."); + 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, + "V1 multisig kex messages are deprecated (unsafe)."); + + // 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)}; + crypto::signature msg_signature; + + if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_1.size()) == MULTISIG_KEX_MSG_V2_MAGIC_1) + { + // try round 1 message + multisig_kex_msg_serializable_round1 kex_msg_rnd1; + + if (::serialization::serialize(b_archive, kex_msg_rnd1)) + { + // in round 1 the message stores a private ancillary key component for the multisig account + // that will be shared by all participants (e.g. a shared private view key) + m_kex_round = 1; + m_msg_privkey = kex_msg_rnd1.msg_privkey; + m_signing_pubkey = kex_msg_rnd1.signing_pubkey; + msg_signature = kex_msg_rnd1.signature; + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_N.size()) == MULTISIG_KEX_MSG_V2_MAGIC_N) + { + // try general message + multisig_kex_msg_serializable_general kex_msg_general; + + if (::serialization::serialize(b_archive, kex_msg_general)) + { + m_kex_round = kex_msg_general.kex_round; + m_msg_privkey = crypto::null_skey; + m_msg_pubkeys = std::move(kex_msg_general.msg_pubkeys); + m_signing_pubkey = kex_msg_general.signing_pubkey; + msg_signature = kex_msg_general.signature; + + CHECK_AND_ASSERT_THROW_MES(m_kex_round > 1, "Invalid kex message round (must be > 1 for the general msg type)."); + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else + { + // unknown message type + CHECK_AND_ASSERT_THROW_MES(false, "Only v2 multisig kex messages are supported."); + } + + // checks + for (const auto &pubkey: m_msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey from message was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(pubkey)), + "Pubkey from message was not in prime subgroup."); + } + + CHECK_AND_ASSERT_THROW_MES(m_signing_pubkey != crypto::null_pkey && m_signing_pubkey != rct::rct2pk(rct::identity()), + "Message signing key was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(m_signing_pubkey)), + "Message signing key was not in prime subgroup."); + + // validate signature + crypto::hash signed_msg{get_msg_to_sign()}; + CHECK_AND_ASSERT_THROW_MES(crypto::check_signature(signed_msg, m_signing_pubkey, msg_signature), + "Multisig kex msg signature invalid."); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h new file mode 100644 index 000000000..833c7c8f6 --- /dev/null +++ b/src/multisig/multisig_kex_msg.h @@ -0,0 +1,109 @@ +// Copyright (c) 2021-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. + +#pragma once + +#include "crypto/crypto.h" + +#include <cstdint> +#include <vector> + + +namespace multisig +{ + //// + // multisig key exchange message + // - can parse and validate an input message + // - can construct and sign a new message + // + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // msg_to_sign = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | b58(msg_content | crypto_sig[signing_privkey](msg_to_sign)) + // + // note: round 1 messages will contain a private key (e.g. for the aggregate multisig private view key) + /// + class multisig_kex_msg final + { + //member types: none + + //constructors + public: + // default constructor + multisig_kex_msg() = default; + + // construct from info + multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey = crypto::null_skey); + + // construct from string + multisig_kex_msg(std::string msg); + + // copy constructor: default + + //destructor: default + ~multisig_kex_msg() = default; + + //overloaded operators: none + + //member functions + // get msg string + const std::string& get_msg() const { return m_msg; } + // get kex round + std::uint32_t get_round() const { return m_kex_round; } + // get msg pubkeys + const std::vector<crypto::public_key>& get_msg_pubkeys() const { return m_msg_pubkeys; } + // get msg privkey + const crypto::secret_key& get_msg_privkey() const { return m_msg_privkey; } + // get msg signing pubkey + const crypto::public_key& get_signing_pubkey() const { return m_signing_pubkey; } + + private: + // msg_to_sign = versioning-domain-sep | kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + crypto::hash get_msg_to_sign() const; + // set: msg string based on msg contents, signing pubkey based on input privkey + void construct_msg(const crypto::secret_key &signing_privkey); + // parse msg string into parts, validate contents and signature + void parse_and_validate_msg(); + + //member variables + private: + // message as string + std::string m_msg; + + // key exchange round this msg was produced for + std::uint32_t m_kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> m_msg_pubkeys; + // privkey stored in msg (if kex round 1) + crypto::secret_key m_msg_privkey; + // pubkey used to sign this msg + crypto::public_key m_signing_pubkey; + }; +} //namespace multisig diff --git a/src/serialization/list.h b/src/multisig/multisig_kex_msg_serialization.h index 411e7800d..e5558cdff 100644 --- a/src/serialization/list.h +++ b/src/multisig/multisig_kex_msg_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2021-2022, The Monero Project // // All rights reserved. // @@ -25,34 +25,54 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include <list> +#include "crypto/crypto.h" +#include "serialization/containers.h" +#include "serialization/crypto.h" +#include "serialization/serialization.h" + +#include <cstdint> +#include <vector> -template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::list<T> &v); -template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::list<T> &v); -namespace serialization +namespace multisig { - namespace detail + /// round 1 kex message + struct multisig_kex_msg_serializable_round1 { - template <typename T> - void do_add(std::list<T> &c, T &&e) - { - c.emplace_back(std::forward<T>(e)); - } - } -} + // privkey stored in msg + crypto::secret_key msg_privkey; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; -#include "serialization.h" + BEGIN_SERIALIZE() + FIELD(msg_privkey) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; -template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::list<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::list<T> &v) { return do_serialize_container(ar, v); } + /// general kex message (if round > 1) + struct multisig_kex_msg_serializable_general + { + // key exchange round this msg was produced for + std::uint32_t kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> msg_pubkeys; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; + BEGIN_SERIALIZE() + VARINT_FIELD(kex_round) + FIELD(msg_pubkeys) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp new file mode 100644 index 000000000..cbc556b71 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -0,0 +1,943 @@ +// 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. + +#include "multisig_tx_builder_ringct.h" + +#include "int-util.h" +#include "memwipe.h" + +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_tx_utils.h" +#include "device/device.hpp" +#include "multisig_clsag_context.h" +#include "ringct/bulletproofs.h" +#include "ringct/bulletproofs_plus.h" +#include "ringct/rctSigs.h" + +#include <boost/multiprecision/cpp_int.hpp> + +#include <algorithm> +#include <cstring> +#include <limits> +#include <set> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool view_tag_required(const int bp_version) +{ + // view tags were introduced at the same time as BP+, so they are needed after BP+ (v4 and later) + if (bp_version <= 3) + return false; + else + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void sort_sources( + std::vector<cryptonote::tx_source_entry>& sources +) +{ + std::sort(sources.begin(), sources.end(), [](const auto& lhs, const auto& rhs){ + const rct::key& ki0 = lhs.multisig_kLRki.ki; + const rct::key& ki1 = rhs.multisig_kLRki.ki; + return memcmp(&ki0, &ki1, sizeof(rct::key)) > 0; + }); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_sources( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + rct::keyV& input_secret_keys +) +{ + const std::size_t num_sources = sources.size(); + hw::device& hwdev = account_keys.get_device(); + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + for (const std::uint32_t minor_index: subaddr_minor_indices) { + subaddresses[hwdev.get_subaddress_spend_public_key( + account_keys, + {subaddr_account, minor_index} + )] = {subaddr_account, minor_index}; + } + input_secret_keys.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const auto& src = sources[i]; + crypto::key_image tmp_key_image; + cryptonote::keypair tmp_keys; + if (src.real_output >= src.outputs.size()) + return false; + if (not cryptonote::generate_key_image_helper( + account_keys, + subaddresses, + rct::rct2pk(src.outputs[src.real_output].second.dest), + src.real_out_tx_key, + src.real_out_additional_tx_keys, + src.real_output_in_tx_index, + tmp_keys, + tmp_key_image, + hwdev + )) { + return false; + } + input_secret_keys[i] = rct::sk2rct(tmp_keys.sec); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void shuffle_destinations( + std::vector<cryptonote::tx_destination_entry>& destinations +) +{ + std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{}); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_extra( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const crypto::secret_key& tx_secret_key, + const crypto::public_key& tx_public_key, + const std::vector<crypto::public_key>& tx_aux_public_keys, + const std::vector<std::uint8_t>& extra, + cryptonote::transaction& tx +) +{ + hw::device &hwdev = account_keys.get_device(); + tx.extra = extra; + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + if (cryptonote::parse_tx_extra(tx.extra, tx_extra_fields)) + { + bool add_dummy_payment_id = true; + cryptonote::tx_extra_nonce extra_nonce; + if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id = crypto::null_hash; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id8); + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + // valid combinations: + // - 1 output with encrypted payment ID, dummy change output (0 amount) + // - 0 outputs, 1 change output with encrypted payment ID + // - 1 output with encrypted payment ID, 1 change output + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce_updated; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_nonce)); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id8); + add_dummy_payment_id = false; + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + add_dummy_payment_id = false; + } + } + + // we don't add one if we've got more than the usual 1 destination plus change + if (destinations.size() > 2) + add_dummy_payment_id = false; + + if (add_dummy_payment_id) + { + // if we have neither long nor short payment id, add a dummy short one, + // this should end up being the vast majority of txes as time goes on + std::string extra_nonce_updated; + crypto::hash8 payment_id8 = crypto::null_hash8; + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + LOG_ERROR("Failed to get key to encrypt dummy payment id with"); + } + else + { + hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key); + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add dummy encrypted payment id to tx extra"); + // continue anyway + } + } + } + } + else + { + MWARNING("Failed to parse tx extra"); + tx_extra_fields.clear(); + } + + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_pub_key)); + cryptonote::add_tx_pub_key_to_extra(tx.extra, tx_public_key); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_additional_pub_keys)); + LOG_PRINT_L2("tx pubkey: " << tx_public_key); + if (tx_aux_public_keys.size()) + { + LOG_PRINT_L2("additional tx pubkeys: "); + for (size_t i = 0; i < tx_aux_public_keys.size(); ++i) + LOG_PRINT_L2(tx_aux_public_keys[i]); + cryptonote::add_additional_tx_pub_keys_to_extra(tx.extra, tx_aux_public_keys); + } + if (not cryptonote::sort_tx_extra(tx.extra, tx.extra)) + return false; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_destinations( + const cryptonote::account_keys& account_keys, + const std::uint32_t subaddr_account, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const std::vector<std::uint8_t>& extra, + const bool use_view_tags, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + rct::keyV& output_public_keys, + rct::keyV& output_amount_secret_keys, + std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + hw::device &hwdev = account_keys.get_device(); + + // check non-zero change amount case + if (change.amount > 0) + { + // the change output must be directed to the local account + if (change.addr != hwdev.get_subaddress(account_keys, {subaddr_account})) + return false; + + // expect the change destination to be in the destination set + if (std::find_if(destinations.begin(), destinations.end(), + [&change](const auto &destination) -> bool + { + return destination.addr == change.addr; + }) == destinations.end()) + return false; + } + + // collect non-change recipients into normal/subaddress buckets + std::unordered_set<cryptonote::account_public_address> unique_subbaddr_recipients; + std::unordered_set<cryptonote::account_public_address> unique_std_recipients; + for(const auto& dst_entr: destinations) { + if (dst_entr.addr == change.addr) + continue; + if (dst_entr.is_subaddress) + unique_subbaddr_recipients.insert(dst_entr.addr); + else + unique_std_recipients.insert(dst_entr.addr); + } + + if (not reconstruction) { + tx_secret_key = rct::rct2sk(rct::skGen()); + } + + // tx pub key: R + crypto::public_key tx_public_key; + if (unique_std_recipients.empty() && unique_subbaddr_recipients.size() == 1) { + // if there is exactly 1 non-change recipient, and it's to a subaddress, then the tx pubkey = r*Ksi_nonchange_recipient + tx_public_key = rct::rct2pk( + hwdev.scalarmultKey( + rct::pk2rct(unique_subbaddr_recipients.begin()->m_spend_public_key), + rct::sk2rct(tx_secret_key) + )); + } + else { + // otherwise, the tx pub key = r*G + // - if there are > 1 non-change recipients, with at least one to a subaddress, then the tx pubkey is not used + // (additional tx keys will be used instead) + // - if all non-change recipients are to normal addresses, then the tx pubkey will be used by all recipients + // (including change recipient, even if change is to a subaddress) + tx_public_key = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_secret_key))); + } + + // additional tx pubkeys: R_t + // - add if there are > 1 non-change recipients, with at least one to a subaddress + const std::size_t num_destinations = destinations.size(); + + const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1; + if (not reconstruction and need_tx_aux_keys) { + tx_aux_secret_keys.clear(); + tx_aux_secret_keys.reserve(num_destinations); + for(std::size_t i = 0; i < num_destinations; ++i) + tx_aux_secret_keys.push_back(rct::rct2sk(rct::skGen())); + } + + output_public_keys.resize(num_destinations); + view_tags.resize(num_destinations); + std::vector<crypto::public_key> tx_aux_public_keys; + crypto::public_key temp_output_public_key; + + for (std::size_t i = 0; i < num_destinations; ++i) { + if (not hwdev.generate_output_ephemeral_keys( + unsigned_tx.version, + account_keys, + tx_public_key, + tx_secret_key, + destinations[i], + change.addr, + i, + need_tx_aux_keys, + tx_aux_secret_keys, + tx_aux_public_keys, + output_amount_secret_keys, + temp_output_public_key, + use_view_tags, + view_tags[i] //unused variable if use_view_tags is not set + )) { + return false; + } + output_public_keys[i] = rct::pk2rct(temp_output_public_key); + } + + if (num_destinations != output_amount_secret_keys.size()) + return false; + + CHECK_AND_ASSERT_MES( + tx_aux_public_keys.size() == tx_aux_secret_keys.size(), + false, + "Internal error creating additional public keys" + ); + + if (not set_tx_extra(account_keys, destinations, change, tx_secret_key, tx_public_key, tx_aux_public_keys, extra, unsigned_tx)) + return false; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void set_tx_inputs( + const std::vector<cryptonote::tx_source_entry>& sources, + cryptonote::transaction& unsigned_tx +) +{ + const std::size_t num_sources = sources.size(); + unsigned_tx.vin.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + std::vector<std::uint64_t> offsets; + offsets.reserve(sources[i].outputs.size()); + for (const auto& e: sources[i].outputs) + offsets.emplace_back(e.first); + unsigned_tx.vin[i] = cryptonote::txin_to_key{ + .amount = 0, + .key_offsets = cryptonote::absolute_output_offsets_to_relative(offsets), + .k_image = rct::rct2ki(sources[i].multisig_kLRki.ki), + }; + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool onetime_addresses_are_unique(const rct::keyV& output_public_keys) +{ + for (auto addr_it = output_public_keys.begin(); addr_it != output_public_keys.end(); ++addr_it) + { + if (std::find(output_public_keys.begin(), addr_it, *addr_it) != addr_it) + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs(const rct::keyV& output_public_keys, cryptonote::transaction& unsigned_tx) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs + const std::size_t num_destinations = output_public_keys.size(); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), false, crypto::view_tag{}, unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs_with_view_tags( + const rct::keyV& output_public_keys, + const std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs (with view tags) + const std::size_t num_destinations = output_public_keys.size(); + CHECK_AND_ASSERT_MES(view_tags.size() == num_destinations, false, + "multisig signing protocol: internal error, view tag size mismatch."); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), true, view_tags[i], unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_new_range_proofs(const int bp_version, + const std::vector<std::uint64_t>& output_amounts, + const rct::keyV& output_amount_masks, + rct::rctSigPrunable& sigs) +{ + sigs.bulletproofs.clear(); + sigs.bulletproofs_plus.clear(); + + if (bp_version == 3) + sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks)); + else if (bp_version == 4) + sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool try_reconstruct_range_proofs(const int bp_version, + const rct::rctSigPrunable& original_sigs, + const std::size_t num_destinations, + const rct::ctkeyV& output_public_keys, + rct::rctSigPrunable& reconstructed_sigs) +{ + auto try_reconstruct_range_proofs = + [&](const auto &original_range_proofs, auto &new_range_proofs) -> bool + { + if (original_range_proofs.size() != 1) + return false; + + new_range_proofs = original_range_proofs; + new_range_proofs[0].V.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + new_range_proofs[0].V[i] = rct::scalarmultKey(output_public_keys[i].mask, rct::INV_EIGHT); + + return true; + }; + + if (bp_version == 3) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs, reconstructed_sigs.bulletproofs)) + return false; + return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs); + } + else if (bp_version == 4) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus)) + return false; + return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus); + } + + return false; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_rct_signatures( + const std::uint64_t fee, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const rct::keyV& input_secret_keys, + const rct::keyV& output_public_keys, + const rct::keyV& output_amount_secret_keys, + const rct::RCTConfig& rct_config, + const bool reconstruction, + cryptonote::transaction& unsigned_tx, + std::vector<CLSAG_context_t>& CLSAG_contexts, + rct::keyV& cached_w +) +{ + if (rct_config.bp_version != 3 && + rct_config.bp_version != 4) + return false; + if (rct_config.range_proof_type != rct::RangeProofPaddedBulletproof) + return false; + + const std::size_t num_destinations = destinations.size(); + const std::size_t num_sources = sources.size(); + + // rct_signatures component of tx + rct::rctSig rv{}; + + // set misc. fields + if (rct_config.bp_version == 3) + rv.type = rct::RCTTypeCLSAG; + else if (rct_config.bp_version == 4) + rv.type = rct::RCTTypeBulletproofPlus; + else + return false; + rv.txnFee = fee; + rv.message = rct::hash2rct(cryptonote::get_transaction_prefix_hash(unsigned_tx)); + + // define outputs + std::vector<std::uint64_t> output_amounts(num_destinations); + rct::keyV output_amount_masks(num_destinations); + rv.ecdhInfo.resize(num_destinations); + rv.outPk.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) { + rv.outPk[i].dest = output_public_keys[i]; + output_amounts[i] = destinations[i].amount; + output_amount_masks[i] = genCommitmentMask(output_amount_secret_keys[i]); + rv.ecdhInfo[i].amount = rct::d2h(output_amounts[i]); + rct::addKeys2( + rv.outPk[i].mask, + output_amount_masks[i], + rv.ecdhInfo[i].amount, + rct::H + ); + rct::ecdhEncode(rv.ecdhInfo[i], output_amount_secret_keys[i], true); + } + + // output range proofs + if (not reconstruction) { + make_new_range_proofs(rct_config.bp_version, output_amounts, output_amount_masks, rv.p); + } + else { + if (not try_reconstruct_range_proofs(rct_config.bp_version, + unsigned_tx.rct_signatures.p, + num_destinations, + rv.outPk, + rv.p)) + return false; + } + + // prepare rings for input CLSAGs + rv.mixRing.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = sources[i].outputs.size(); + rv.mixRing[i].resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + rv.mixRing[i][j].dest = sources[i].outputs[j].second.dest; + rv.mixRing[i][j].mask = sources[i].outputs[j].second.mask; + } + } + + // make pseudo-output commitments + rct::keyV a; //pseudo-output commitment blinding factors + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(a.data()), a.size() * sizeof(rct::key)); + }); + if (not reconstruction) { + a.resize(num_sources); + rv.p.pseudoOuts.resize(num_sources); + a[num_sources - 1] = rct::zero(); + for (std::size_t i = 0; i < num_destinations; ++i) { + sc_add( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + output_amount_masks[i].bytes + ); + } + for (std::size_t i = 0; i < num_sources - 1; ++i) { + rct::skGen(a[i]); + sc_sub( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + a[i].bytes + ); + rct::genC(rv.p.pseudoOuts[i], a[i], sources[i].amount); + } + rct::genC( + rv.p.pseudoOuts[num_sources - 1], + a[num_sources - 1], + sources[num_sources - 1].amount + ); + } + // check balance if reconstructing the tx + else { + rv.p.pseudoOuts = unsigned_tx.rct_signatures.p.pseudoOuts; + if (num_sources != rv.p.pseudoOuts.size()) + return false; + rct::key balance_accumulator = rct::scalarmultH(rct::d2h(fee)); + for (const auto& e: rv.outPk) + rct::addKeys(balance_accumulator, balance_accumulator, e.mask); + for (const auto& pseudoOut: rv.p.pseudoOuts) + rct::subKeys(balance_accumulator, balance_accumulator, pseudoOut); + if (not (balance_accumulator == rct::identity())) + return false; + } + + // prepare input CLSAGs for signing + const rct::key message = get_pre_mlsag_hash(rv, hw::get_device("default")); + + rv.p.CLSAGs.resize(num_sources); + if (reconstruction) { + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + } + + CLSAG_contexts.resize(num_sources); + if (not reconstruction) + cached_w.resize(num_sources); + + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = rv.mixRing[i].size(); + const rct::key& I = sources[i].multisig_kLRki.ki; + const std::size_t l = sources[i].real_output; + if (l >= ring_size) + return false; + rct::keyV& s = rv.p.CLSAGs[i].s; + const rct::key& C_offset = rv.p.pseudoOuts[i]; + rct::keyV P(ring_size); + rct::keyV C_nonzero(ring_size); + + if (not reconstruction) { + s.resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + if (j != l) + s[j] = rct::skGen(); //make fake responses + } + } + else { + if (ring_size != unsigned_tx.rct_signatures.p.CLSAGs[i].s.size()) + return false; + s = unsigned_tx.rct_signatures.p.CLSAGs[i].s; + } + + for (std::size_t j = 0; j < ring_size; ++j) { + P[j] = rv.mixRing[i][j].dest; + C_nonzero[j] = rv.mixRing[i][j].mask; + } + + rct::key D; + rct::key z; + auto z_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&z), sizeof(rct::key)); + }); + if (not reconstruction) { + sc_sub(z.bytes, sources[i].mask.bytes, a[i].bytes); //commitment to zero privkey + ge_p3 H_p3; + rct::hash_to_p3(H_p3, rv.mixRing[i][l].dest); + rct::key H_l; + ge_p3_tobytes(H_l.bytes, &H_p3); + D = rct::scalarmultKey(H_l, z); //auxilliary key image (for commitment to zero) + rv.p.CLSAGs[i].D = rct::scalarmultKey(D, rct::INV_EIGHT); + rv.p.CLSAGs[i].I = I; + } + else { + rv.p.CLSAGs[i].D = unsigned_tx.rct_signatures.p.CLSAGs[i].D; + rv.p.CLSAGs[i].I = I; + D = rct::scalarmultKey(rv.p.CLSAGs[i].D, rct::EIGHT); + } + + if (not CLSAG_contexts[i].init(P, C_nonzero, C_offset, message, I, D, l, s, kAlphaComponents)) + return false; + + if (not reconstruction) { + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + sc_mul(cached_w[i].bytes, mu_P.bytes, input_secret_keys[i].bytes); + sc_muladd(cached_w[i].bytes, mu_C.bytes, z.bytes, cached_w[i].bytes); + } + } + unsigned_tx.rct_signatures = std::move(rv); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_tx_fee( + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + std::uint64_t& fee +) +{ + boost::multiprecision::uint128_t in_amount = 0; + for (const auto& src: sources) + in_amount += src.amount; + + boost::multiprecision::uint128_t out_amount = 0; + for (const auto& dst: destinations) + out_amount += dst.amount; + + if (out_amount > in_amount) + return false; + + if (in_amount - out_amount > std::numeric_limits<std::uint64_t>::max()) + return false; + + fee = static_cast<std::uint64_t>(in_amount - out_amount); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::tx_builder_ringct_t(): initialized(false) {} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::~tx_builder_ringct_t() +{ + memwipe(static_cast<rct::key *>(cached_w.data()), cached_w.size() * sizeof(rct::key)); +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + cryptonote::transaction& unsigned_tx +) +{ + initialized = false; + this->reconstruction = reconstruction; + if (not use_rct) + return false; + if (sources.empty()) + return false; + + if (not reconstruction) + unsigned_tx.set_null(); + + std::uint64_t fee; + if (not compute_tx_fee(sources, destinations, fee)) + return false; + + // decide if view tags are needed + const bool use_view_tags{view_tag_required(rct_config.bp_version)}; + + // misc. fields + unsigned_tx.version = 2; //rct = 2 + unsigned_tx.unlock_time = unlock_time; + + // sort inputs + sort_sources(sources); + + // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature) + rct::keyV input_secret_keys; + auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(input_secret_keys.data()), input_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_sources(account_keys, sources, subaddr_account, subaddr_minor_indices, input_secret_keys)) + return false; + + // randomize output order + if (not reconstruction) + shuffle_destinations(destinations); + + // prepare outputs + rct::keyV output_public_keys; + rct::keyV output_amount_secret_keys; + std::vector<crypto::view_tag> view_tags; + auto output_amount_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(output_amount_secret_keys.data()), output_amount_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_destinations(account_keys, + subaddr_account, + destinations, + change, + extra, + use_view_tags, + reconstruction, + tx_secret_key, + tx_aux_secret_keys, + output_public_keys, + output_amount_secret_keys, + view_tags, + unsigned_tx)) + return false; + + // add inputs to tx + set_tx_inputs(sources, unsigned_tx); + + // add output one-time addresses to tx + bool set_tx_outputs_result{false}; + if (use_view_tags) + set_tx_outputs_result = set_tx_outputs_with_view_tags(output_public_keys, view_tags, unsigned_tx); + else + set_tx_outputs_result = set_tx_outputs(output_public_keys, unsigned_tx); + + if (not set_tx_outputs_result) + return false; + + // prepare input signatures + if (not set_tx_rct_signatures(fee, sources, destinations, input_secret_keys, output_public_keys, output_amount_secret_keys, + rct_config, reconstruction, unsigned_tx, CLSAG_contexts, cached_w)) + return false; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s +) +{ + if (not initialized or reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (source >= num_sources) + return false; + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[source].combine_alpha_and_compute_challenge( + total_alpha_G, + total_alpha_H, + alpha, + alpha_combined, + c_0, + c + )) { + return false; + } + + // initial partial response: + // s = alpha_combined_local - challenge*[mu_P*(local keys and sender-receiver secret and subaddress material) + + // mu_C*(commitment-to-zero secret)] + sc_mulsub(s.bytes, c.bytes, cached_w[source].bytes, alpha_combined.bytes); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s +) +{ + if (not initialized or not reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (num_sources != total_alpha_G.size()) + return false; + if (num_sources != total_alpha_H.size()) + return false; + if (num_sources != alpha.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + for (std::size_t i = 0; i < num_sources; ++i) { + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[i].combine_alpha_and_compute_challenge( + total_alpha_G[i], + total_alpha_H[i], + alpha[i], + alpha_combined, + c_0[i], + c + )) { + return false; + } + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + rct::key w; + auto w_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&w), sizeof(rct::key)); + }); + sc_mul(w.bytes, mu_P.bytes, x.bytes); + + // include local signer's response: + // s += alpha_combined_local - challenge*[mu_P*(local keys)] + sc_add(s[i].bytes, s[i].bytes, alpha_combined.bytes); + sc_mulsub(s[i].bytes, c.bytes, w.bytes, s[i].bytes); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx +) +{ + const std::size_t num_sources = sources.size(); + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size(); + if (sources[i].real_output >= ring_size) + return false; + unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i]; + unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i]; + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h new file mode 100644 index 000000000..67ef9e065 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -0,0 +1,119 @@ +// 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 "ringct/rctTypes.h" + +#include <set> +#include <vector> + +namespace cryptonote { + +class transaction; +struct tx_source_entry; +struct tx_destination_entry; +struct account_keys; + +} + +namespace multisig { + +namespace signing { + +class CLSAG_context_t; + +// number of parallel signing nonces to use per signer (2 nonces as in musig2 and FROST) +constexpr std::size_t kAlphaComponents = 2; + +class tx_builder_ringct_t final { +private: + // the tx builder has been initialized + bool initialized; + // the tx builder is 'reconstructing' a tx that has already been created using this object + bool reconstruction; + // cached: mu_P*(local keys and sender-receiver secret and subaddress material) + mu_C*(commitment-to-zero secret) + // - these are only used for the initial building of a tx (not reconstructions) + rct::keyV cached_w; + // contexts for making CLSAG challenges with multisig nonces + std::vector<CLSAG_context_t> CLSAG_contexts; +public: + tx_builder_ringct_t(); + ~tx_builder_ringct_t(); + + // prepare an unsigned transaction (and get tx privkeys for outputs) + bool init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + cryptonote::transaction& unsigned_tx + ); + + // get the first partial signature for the specified input ('source') + bool first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s + ); + + // get intermediate partial signatures for all the inputs + bool next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s + ); + + // finalize an unsigned transaction (add challenges and real responses to incomplete CLSAG signatures) + static bool finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx + ); +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index e93e27bcd..a4d31eedf 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -1,4 +1,5 @@ -# Copyright (c) 2018, The Monero Project +# Copyright (c) 2018-2022, The Monero Project + # # All rights reserved. # @@ -28,8 +29,7 @@ set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp resolve.cpp socks.cpp socks_connect.cpp tor_address.cpp zmq.cpp) -set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h resolve.h - socks_connect.h tor_address.h zmq.h) +monero_find_all_headers(net_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_add_library(net ${net_sources} ${net_headers}) target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY}) diff --git a/src/net/dandelionpp.cpp b/src/net/dandelionpp.cpp index e0d2821e5..e75fb2753 100644 --- a/src/net/dandelionpp.cpp +++ b/src/net/dandelionpp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/dandelionpp.h b/src/net/dandelionpp.h index 345f868af..e344bc7ce 100644 --- a/src/net/dandelionpp.h +++ b/src/net/dandelionpp.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/error.cpp b/src/net/error.cpp index d2e713bc5..254db7ae1 100644 --- a/src/net/error.cpp +++ b/src/net/error.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/error.h b/src/net/error.h index 746eb0ecb..969eefc41 100644 --- a/src/net/error.h +++ b/src/net/error.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/fwd.h b/src/net/fwd.h index 7cae88251..b105da115 100644 --- a/src/net/fwd.h +++ b/src/net/fwd.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/http.cpp b/src/net/http.cpp index f54b65c29..c0ed3d430 100644 --- a/src/net/http.cpp +++ b/src/net/http.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/http.h b/src/net/http.h index 91a345851..bbb8ee984 100644 --- a/src/net/http.h +++ b/src/net/http.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/i2p_address.cpp b/src/net/i2p_address.cpp index ada4eb0d3..b0194a525 100644 --- a/src/net/i2p_address.cpp +++ b/src/net/i2p_address.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/i2p_address.h b/src/net/i2p_address.h index 5a694419d..7f6ef1b3f 100644 --- a/src/net/i2p_address.h +++ b/src/net/i2p_address.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/parse.cpp b/src/net/parse.cpp index 298576ba4..1df6175b4 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/parse.h b/src/net/parse.h index 4d5efe161..648076d7b 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/resolve.cpp b/src/net/resolve.cpp index 1b43cf6c4..f4881ffe5 100644 --- a/src/net/resolve.cpp +++ b/src/net/resolve.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/resolve.h b/src/net/resolve.h index 46bd8e617..39b1d1830 100644 --- a/src/net/resolve.h +++ b/src/net/resolve.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/socks.cpp b/src/net/socks.cpp index c2330bd41..97ef4a672 100644 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // @@ -321,8 +321,9 @@ namespace socks { if (self && self->proxy_.is_open()) { - self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - self->proxy_.close(); + boost::system::error_code ec; + self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + self->proxy_.close(ec); } }); } diff --git a/src/net/socks.h b/src/net/socks.h index 506b53195..af67d4abe 100644 --- a/src/net/socks.h +++ b/src/net/socks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/socks_connect.cpp b/src/net/socks_connect.cpp index c797a24d4..5317564de 100644 --- a/src/net/socks_connect.cpp +++ b/src/net/socks_connect.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/socks_connect.h b/src/net/socks_connect.h index 45bb10efe..587e3cd3c 100644 --- a/src/net/socks_connect.h +++ b/src/net/socks_connect.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index a04dcb042..ac36dbffd 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/tor_address.h b/src/net/tor_address.h index 22d8cc119..5b036f0d4 100644 --- a/src/net/tor_address.h +++ b/src/net/tor_address.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/net/zmq.cpp b/src/net/zmq.cpp index 15560ca7e..2b3ca8376 100644 --- a/src/net/zmq.cpp +++ b/src/net/zmq.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // @@ -134,7 +134,7 @@ namespace zmq { /* ZMQ documentation states that message parts are atomic - either all are received or none are. Looking through ZMQ code and - Github discussions indicates that after part 1 is returned, + GitHub discussions indicates that after part 1 is returned, `EAGAIN` cannot be returned to meet these guarantees. Unit tests verify (for the `inproc://` case) that this is the behavior. Therefore, read errors after the first part are treated as a diff --git a/src/net/zmq.h b/src/net/zmq.h index fa4ef2fc9..18bb80c8b 100644 --- a/src/net/zmq.h +++ b/src/net/zmq.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index 0cd38f253..af58d2bb0 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index d9050200a..f9803fd81 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 9e64121be..98d8ecfff 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -111,15 +111,11 @@ namespace nodetool struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { p2p_connection_context_t() - : fluff_txs(), - flush_time(std::chrono::steady_clock::time_point::max()), - peer_id(0), + : peer_id(0), support_flags(0), m_in_timedsync(false) {} - std::vector<cryptonote::blobdata> fluff_txs; - std::chrono::steady_clock::time_point flush_time; peerid_type peer_id; uint32_t support_flags; bool m_in_timedsync; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index ac65a57c1..f33ce977d 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -55,7 +55,6 @@ #include "math_helper.h" #include "misc_log_ex.h" #include "p2p_protocol_defs.h" -#include "net/local_ip.h" #include "crypto/crypto.h" #include "storages/levin_abstract_invoke2.h" #include "cryptonote_core/cryptonote_core.h" @@ -539,7 +538,7 @@ namespace nodetool if ( !set_max_out_peers(public_zone, command_line::get_arg(vm, arg_out_peers) ) ) return false; else - m_payload_handler.set_max_out_peers(public_zone.m_config.m_net_config.max_out_connection_count); + m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, public_zone.m_config.m_net_config.max_out_connection_count); if ( !set_max_in_peers(public_zone, command_line::get_arg(vm, arg_in_peers) ) ) @@ -576,6 +575,8 @@ namespace nodetool if (!set_max_out_peers(zone, proxy.max_connections)) return false; + else + m_payload_handler.set_max_out_peers(proxy.zone, proxy.max_connections); epee::byte_slice this_noise = nullptr; if (proxy.noise) @@ -696,14 +697,14 @@ namespace nodetool { full_addrs.insert("212.83.175.67:28080"); full_addrs.insert("212.83.172.165:28080"); - full_addrs.insert("192.110.160.146:28080"); + full_addrs.insert("176.9.0.187:28080"); full_addrs.insert("88.99.173.38:28080"); full_addrs.insert("51.79.173.165:28080"); } else if (m_nettype == cryptonote::STAGENET) { full_addrs.insert("162.210.173.150:38080"); - full_addrs.insert("192.110.160.146:38080"); + full_addrs.insert("176.9.0.187:38080"); full_addrs.insert("88.99.173.38:38080"); full_addrs.insert("51.79.173.165:38080"); } @@ -714,10 +715,10 @@ namespace nodetool { full_addrs.insert("212.83.175.67:18080"); full_addrs.insert("212.83.172.165:18080"); - full_addrs.insert("192.110.160.146:18080"); + full_addrs.insert("176.9.0.187:18080"); full_addrs.insert("88.198.163.90:18080"); full_addrs.insert("95.217.25.101:18080"); - full_addrs.insert("209.250.243.248:18080"); + full_addrs.insert("136.244.105.131:18080"); full_addrs.insert("104.238.221.81:18080"); full_addrs.insert("66.85.74.134:18080"); full_addrs.insert("88.99.173.38:18080"); @@ -1429,6 +1430,7 @@ namespace nodetool ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr); zone.m_peerlist.append_with_peer_anchor(ape); + zone.m_notifier.on_handshake_complete(con->m_connection_id, con->m_is_income); zone.m_notifier.new_out_connection(); LOG_DEBUG_CC(*con, "CONNECTION HANDSHAKED OK."); @@ -2462,8 +2464,12 @@ namespace nodetool const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(zone_type); + //will add self to peerlist if in same zone as outgoing later in this function + const bool outgoing_to_same_zone = !context.m_is_income && zone.m_our_address.get_zone() == zone_type; + const uint32_t max_peerlist_size = P2P_DEFAULT_PEERS_IN_HANDSHAKE - (outgoing_to_same_zone ? 1 : 0); + std::vector<peerlist_entry> local_peerlist_new; - zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE); + zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, max_peerlist_size); //only include out peers we did not already send rsp.local_peerlist_new.reserve(local_peerlist_new.size()); @@ -2483,7 +2489,7 @@ namespace nodetool etc., because someone could give faulty addresses over Tor/I2P to get the real peer with that identity banned/blacklisted. */ - if(!context.m_is_income && zone.m_our_address.get_zone() == zone_type) + if(outgoing_to_same_zone) rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, zone.m_config.m_peer_id, std::time(nullptr)}); LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); @@ -2543,6 +2549,8 @@ namespace nodetool return 1; } + zone.m_notifier.on_handshake_complete(context.m_connection_id, context.m_is_income); + if(has_too_many_connections(context.m_remote_address)) { LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address"); @@ -2669,6 +2677,9 @@ namespace nodetool zone.m_peerlist.remove_from_peer_anchor(na); } + if (!zone.m_net_server.is_stop_signal_sent()) { + zone.m_notifier.on_connection_close(context.m_connection_id); + } m_payload_handler.on_connection_close(context); MINFO("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); @@ -2753,7 +2764,7 @@ namespace nodetool public_zone->second.m_config.m_net_config.max_out_connection_count = count; if(current > count) public_zone->second.m_net_server.get_config_object().del_out_connections(current - count); - m_payload_handler.set_max_out_peers(count); + m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, count); } } @@ -2882,10 +2893,12 @@ namespace nodetool { if (m_offline) return true; if (!m_exclusive_peers.empty()) return true; - if (m_payload_handler.needs_new_sync_connections()) return true; for (auto& zone : m_network_zones) { + if (m_payload_handler.needs_new_sync_connections(zone.first)) + continue; + if (zone.second.m_net_server.is_stop_signal_sent()) return false; diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 92b7596ae..ef4552f44 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_peerlist.cpp b/src/p2p/net_peerlist.cpp index 50dc6da77..3e132c91f 100644 --- a/src/p2p/net_peerlist.cpp +++ b/src/p2p/net_peerlist.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 0662789b9..dc480462d 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -31,6 +31,7 @@ #pragma once #include <iosfwd> +#include <iterator> #include <list> #include <string> #include <vector> @@ -46,7 +47,6 @@ #include "crypto/crypto.h" #include "cryptonote_config.h" #include "net/enums.h" -#include "net/local_ip.h" #include "p2p_protocol_defs.h" #include "syncobj.h" @@ -184,6 +184,7 @@ namespace nodetool private: void trim_white_peerlist(); void trim_gray_peerlist(); + static peerlist_entry get_nth_latest_peer(peers_indexed& peerlist, size_t n); friend class boost::serialization::access; epee::critical_section m_peerlist_lock; @@ -214,6 +215,16 @@ namespace nodetool } } //-------------------------------------------------------------------------------------------------- + inline + peerlist_entry peerlist_manager::get_nth_latest_peer(peers_indexed& peerlist, const size_t n) + { + // Is not thread-safe nor does it check bounds. Do this before calling. Indexing starts at 0. + peers_indexed::index<by_time>::type& by_time_index = peerlist.get<by_time>(); + auto by_time_it = --by_time_index.end(); + std::advance(by_time_it, -static_cast<long long>(n)); + return *by_time_it; + } + //-------------------------------------------------------------------------------------------------- inline bool peerlist_manager::merge_peerlist(const std::vector<peerlist_entry>& outer_bs, const std::function<bool(const peerlist_entry&)> &f) { @@ -235,8 +246,7 @@ namespace nodetool if(i >= m_peers_white.size()) return false; - peers_indexed::index<by_time>::type& by_time_index = m_peers_white.get<by_time>(); - p = *epee::misc_utils::move_it_backward(--by_time_index.end(), i); + p = peerlist_manager::get_nth_latest_peer(m_peers_white, i); return true; } //-------------------------------------------------------------------------------------------------- @@ -247,8 +257,7 @@ namespace nodetool if(i >= m_peers_gray.size()) return false; - peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); - p = *epee::misc_utils::move_it_backward(--by_time_index.end(), i); + p = peerlist_manager::get_nth_latest_peer(m_peers_gray, i); return true; } //-------------------------------------------------------------------------------------------------- @@ -437,9 +446,7 @@ namespace nodetool } size_t random_index = crypto::rand_idx(m_peers_gray.size()); - - peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); - pe = *epee::misc_utils::move_it_backward(--by_time_index.end(), random_index); + pe = peerlist_manager::get_nth_latest_peer(m_peers_gray, random_index); return true; diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 1cf4cb026..7d8bd1480 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 12763d4ee..8b9cd0f09 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -30,6 +30,7 @@ #pragma once +#include <iomanip> #include <boost/uuid/uuid.hpp> #include <boost/serialization/version.hpp> #include "serialization/keyvalue_serialization.h" diff --git a/src/platform/mingw/alloca.h b/src/platform/mingw/alloca.h index b514051aa..10a9db198 100644 --- a/src/platform/mingw/alloca.h +++ b/src/platform/mingw/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/alloca.h b/src/platform/msc/alloca.h index a9ca8cc84..3b197bf6d 100644 --- a/src/platform/msc/alloca.h +++ b/src/platform/msc/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/inline_c.h b/src/platform/msc/inline_c.h index 2a4952cff..36d3d0026 100644 --- a/src/platform/msc/inline_c.h +++ b/src/platform/msc/inline_c.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/stdbool.h b/src/platform/msc/stdbool.h index 445b625b4..d4932e8f8 100644 --- a/src/platform/msc/stdbool.h +++ b/src/platform/msc/stdbool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/sys/param.h b/src/platform/msc/sys/param.h index e5d208003..61002e1a1 100644 --- a/src/platform/msc/sys/param.h +++ b/src/platform/msc/sys/param.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index 40b2dfd55..d415e6409 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2020, The Monero Project +# Copyright (c) 2016-2022, The Monero Project # # All rights reserved. # @@ -31,13 +31,10 @@ set(ringct_basic_sources rctTypes.cpp rctCryptoOps.c multiexp.cc - bulletproofs.cc) + bulletproofs.cc + bulletproofs_plus.cc) -set(ringct_basic_private_headers - rctOps.h - rctTypes.h - multiexp.h - bulletproofs.h) +monero_find_all_headers(ringct_basic_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(ringct_basic ${crypto_private_headers}) diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index a6e12c9b3..ba7ab2c0d 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -70,13 +70,12 @@ static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS; -static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr<straus_cached_data> straus_HiGi_cache; static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; -static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; -static const rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; -static const rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; +static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; +static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; +static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); static const rct::key ip12 = inner_product(oneN, twoN); @@ -100,8 +99,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT); - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_EXPONENT + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -121,10 +119,10 @@ static void init_exponents() data.reserve(maxN*maxM*2); for (size_t i = 0; i < maxN*maxM; ++i) { - Hi[i] = get_exponent(rct::H, i * 2); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed"); - Gi[i] = get_exponent(rct::H, i * 2 + 1); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Hi = get_exponent(rct::H, i * 2); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Gi = get_exponent(rct::H, i * 2 + 1); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed"); data.push_back({rct::zero(), Gi_p3[i]}); data.push_back({rct::zero(), Hi_p3[i]}); @@ -133,11 +131,10 @@ static void init_exponents() straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); - MINFO("Hi/Gi cache size: " << (sizeof(Hi)+sizeof(Gi))/1024 << " kB"); MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB"); MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB"); MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB"); - size_t cache_size = (sizeof(Hi)+sizeof(Hi_p3))*2 + straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); + size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Total cache size: " << cache_size/1024 << "kB"); init_done = true; } @@ -895,7 +892,8 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); - const std::vector<rct::key> inverses = invert(to_invert); + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert); // setup weighted aggregates diff --git a/src/ringct/bulletproofs.h b/src/ringct/bulletproofs.h index d8a9fa4ff..c3fe6f8b9 100644 --- a/src/ringct/bulletproofs.h +++ b/src/ringct/bulletproofs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc new file mode 100644 index 000000000..77b800064 --- /dev/null +++ b/src/ringct/bulletproofs_plus.cc @@ -0,0 +1,1121 @@ +// Copyright (c) 2017-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. + +// Implements the Bulletproofs+ prover and verifier algorithms +// +// Preprint: https://eprint.iacr.org/2020/735, version 17 Jun 2020 +// +// NOTE ON NOTATION: +// In the signature constructions used in Monero, commitments to zero are treated as +// public keys against the curve group generator `G`. This means that amount +// commitments must use another generator `H` for values in order to show balance. +// The result is that the roles of `g` and `h` in the preprint are effectively swapped +// in this code, taking on the roles of `H` and `G`, respectively. Read carefully! + +#include <stdlib.h> +#include <boost/thread/mutex.hpp> +#include <boost/thread/lock_guard.hpp> +#include "misc_log_ex.h" +#include "span.h" +#include "cryptonote_config.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "rctOps.h" +#include "multiexp.h" +#include "bulletproofs_plus.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bulletproof_plus" + +#define STRAUS_SIZE_LIMIT 232 +#define PIPPENGER_SIZE_LIMIT 0 + +namespace rct +{ + // Vector functions + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n); + + // Proof bounds + static constexpr size_t maxN = 64; // maximum number of bits in range + static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + + // Cached public generators + static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; + static std::shared_ptr<straus_cached_data> straus_HiGi_cache; + static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; + + // Useful scalar constants + static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 + static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 + static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 + static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 + static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) + static rct::key TWO_SIXTY_FOUR_MINUS_ONE; // 2**64 - 1 + + // Initial transcript hash + static rct::key initial_transcript; + + static boost::mutex init_mutex; + + // Use the generator caches to compute a multiscalar multiplication + static inline rct::key multiexp(const std::vector<MultiexpData> &data, size_t HiGi_size) + { + if (HiGi_size > 0) + { + static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT"); + return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size())); + } + else + { + return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size())); + } + } + + // Confirm that a scalar is properly reduced + static inline bool is_reduced(const rct::key &scalar) + { + return sc_check(scalar.bytes) == 0; + } + + // Use hashed values to produce indexed public generators + static ge_p3 get_exponent(const rct::key &base, size_t idx) + { + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + rct::key generator; + ge_p3 generator_p3; + rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); + ge_p3_tobytes(generator.bytes, &generator_p3); + CHECK_AND_ASSERT_THROW_MES(!(generator == rct::identity()), "Exponent is point at infinity"); + return generator_p3; + } + + // Construct public generators + static void init_exponents() + { + boost::lock_guard<boost::mutex> lock(init_mutex); + + // Only needs to be done once + static bool init_done = false; + if (init_done) + return; + + std::vector<MultiexpData> data; + data.reserve(maxN*maxM*2); + for (size_t i = 0; i < maxN*maxM; ++i) + { + Hi_p3[i] = get_exponent(rct::H, i * 2); + Gi_p3[i] = get_exponent(rct::H, i * 2 + 1); + + data.push_back({rct::zero(), Gi_p3[i]}); + data.push_back({rct::zero(), Hi_p3[i]}); + } + + straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); + pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); + + // Compute 2**64 - 1 for later use in simplifying verification + TWO_SIXTY_FOUR_MINUS_ONE = TWO; + for (size_t i = 0; i < 6; i++) + { + sc_mul(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes); + } + sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); + + // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs + const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + ge_p3 initial_transcript_p3; + rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); + ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); + + init_done = true; + } + + // Given two scalar arrays, construct a vector pre-commitment: + // + // a = (a_0, ..., a_{n-1}) + // b = (b_0, ..., b_{n-1}) + // + // Outputs a_0*Gi_0 + ... + a_{n-1}*Gi_{n-1} + + // b_0*Hi_0 + ... + b_{n-1}*Hi_{n-1} + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(a.size()*2); + for (size_t i = 0; i < a.size(); ++i) + { + multiexp_data.emplace_back(a[i], Gi_p3[i]); + multiexp_data.emplace_back(b[i], Hi_p3[i]); + } + return multiexp(multiexp_data, 2 * a.size()); + } + + // Helper function used to compute the L and R terms used in the inner-product round function + static rct::key compute_LR(size_t size, const rct::key &y, const std::vector<ge_p3> &G, size_t G0, const std::vector<ge_p3> &H, size_t H0, const rct::keyV &a, size_t a0, const rct::keyV &b, size_t b0, const rct::key &c, const rct::key &d) + { + CHECK_AND_ASSERT_THROW_MES(size + G0 <= G.size(), "Incompatible size for G"); + CHECK_AND_ASSERT_THROW_MES(size + H0 <= H.size(), "Incompatible size for H"); + CHECK_AND_ASSERT_THROW_MES(size + a0 <= a.size(), "Incompatible size for a"); + CHECK_AND_ASSERT_THROW_MES(size + b0 <= b.size(), "Incompatible size for b"); + CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.resize(size*2 + 2); + rct::key temp; + for (size_t i = 0; i < size; ++i) + { + sc_mul(temp.bytes, a[a0+i].bytes, y.bytes); + sc_mul(multiexp_data[i*2].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + multiexp_data[i*2].point = G[G0+i]; + + sc_mul(multiexp_data[i*2+1].scalar.bytes, b[b0+i].bytes, INV_EIGHT.bytes); + multiexp_data[i*2+1].point = H[H0+i]; + } + + sc_mul(multiexp_data[2*size].scalar.bytes, c.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + multiexp_data[2*size].point = H_p3; + + sc_mul(multiexp_data[2*size+1].scalar.bytes, d.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + multiexp_data[2*size+1].point = G_p3; + + return multiexp(multiexp_data, 0); + } + + // Given a scalar, construct a vector of its powers: + // + // Output (1,x,x**2,...,x**{n-1}) + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::keyV res(n); + res[0] = rct::identity(); + if (n == 1) + return res; + res[1] = x; + for (size_t i = 2; i < n; ++i) + { + sc_mul(res[i].bytes, res[i-1].bytes, x.bytes); + } + return res; + } + + // Given a scalar, construct the sum of its powers from 2 to n (where n is a power of 2): + // + // Output x**2 + x**4 + x**6 + ... + x**n + static rct::key sum_of_even_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES((n & (n - 1)) == 0, "Need n to be a power of 2"); + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key x1 = copy(x); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + + rct::key res = copy(x1); + while (n > 2) + { + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + n /= 2; + } + + return res; + } + + // Given a scalar, return the sum of its powers from 1 to n + // + // Output x**1 + x**2 + x**3 + ... + x**n + static rct::key sum_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key res = ONE; + if (n == 1) + return x; + + n += 1; + rct::key x1 = copy(x); + + const bool is_power_of_2 = (n & (n - 1)) == 0; + if (is_power_of_2) + { + sc_add(res.bytes, res.bytes, x1.bytes); + while (n > 2) + { + sc_mul(x1.bytes, x1.bytes, x1.bytes); + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + n /= 2; + } + } + else + { + rct::key prev = x1; + for (size_t i = 1; i < n; ++i) + { + if (i > 1) + sc_mul(prev.bytes, prev.bytes, x1.bytes); + sc_add(res.bytes, res.bytes, prev.bytes); + } + } + sc_sub(res.bytes, res.bytes, ONE.bytes); + + return res; + } + + // Given two scalar arrays, construct the weighted inner product against another scalar + // + // Output a_0*b_0*y**1 + a_1*b_1*y**2 + ... + a_{n-1}*b_{n-1}*y**n + static rct::key weighted_inner_product(const epee::span<const rct::key> &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::key res = rct::zero(); + rct::key y_power = ONE; + rct::key temp; + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(temp.bytes, a[i].bytes, b[i].bytes); + sc_mul(y_power.bytes, y_power.bytes, y.bytes); + sc_muladd(res.bytes, temp.bytes, y_power.bytes, res.bytes); + } + return res; + } + + static rct::key weighted_inner_product(const rct::keyV &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + return weighted_inner_product(epee::to_span(a), b, y); + } + + // Fold inner-product point vectors + static void hadamard_fold(std::vector<ge_p3> &v, const rct::key &a, const rct::key &b) + { + CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even"); + const size_t sz = v.size() / 2; + for (size_t n = 0; n < sz; ++n) + { + ge_dsmp c[2]; + ge_dsm_precomp(c[0], &v[n]); + ge_dsm_precomp(c[1], &v[sz + n]); + ge_double_scalarmult_precomp_vartime2_p3(&v[n], a.bytes, c[0], b.bytes, c[1]); + } + v.resize(sz); + } + + // Add vectors componentwise + static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b[i].bytes); + } + return res; + } + + // Add a scalar to all elements of a vector + static rct::keyV vector_add(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Subtract a scalar from all elements of a vector + static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_sub(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Multiply a scalar by all elements of a vector + static rct::keyV vector_scalar(const epee::span<const rct::key> &a, const rct::key &x) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(res[i].bytes, a[i].bytes, x.bytes); + } + return res; + } + + // Inversion helper function + static rct::key sm(rct::key y, int n, const rct::key &x) + { + while (n--) + sc_mul(y.bytes, y.bytes, y.bytes); + sc_mul(y.bytes, y.bytes, x.bytes); + return y; + } + + // Compute the inverse of a nonzero + static rct::key invert(const rct::key &x) + { + CHECK_AND_ASSERT_THROW_MES(!(x == ZERO), "Cannot invert zero!"); + rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111; + + _1 = x; + sc_mul(_10.bytes, _1.bytes, _1.bytes); + sc_mul(_100.bytes, _10.bytes, _10.bytes); + sc_mul(_11.bytes, _10.bytes, _1.bytes); + sc_mul(_101.bytes, _10.bytes, _11.bytes); + sc_mul(_111.bytes, _10.bytes, _101.bytes); + sc_mul(_1001.bytes, _10.bytes, _111.bytes); + sc_mul(_1011.bytes, _10.bytes, _1001.bytes); + sc_mul(_1111.bytes, _100.bytes, _1011.bytes); + + rct::key inv; + sc_mul(inv.bytes, _1111.bytes, _1.bytes); + + inv = sm(inv, 123 + 3, _101); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 4, _1001); + inv = sm(inv, 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 3, _101); + inv = sm(inv, 3 + 3, _101); + inv = sm(inv, 3, _111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 2 + 3, _111); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 2 + 4, _1011); + inv = sm(inv, 6 + 4, _1001); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 1 + 4, _1001); + inv = sm(inv, 1 + 3, _111); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 3, _101); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 3, _101); + inv = sm(inv, 1 + 2, _11); + + return inv; + } + + // Invert a batch of scalars, all of which _must_ be nonzero + static rct::keyV invert(rct::keyV x) + { + rct::keyV scratch; + scratch.reserve(x.size()); + + rct::key acc = rct::identity(); + for (size_t n = 0; n < x.size(); ++n) + { + CHECK_AND_ASSERT_THROW_MES(!(x[n] == ZERO), "Cannot invert zero!"); + scratch.push_back(acc); + if (n == 0) + acc = x[0]; + else + sc_mul(acc.bytes, acc.bytes, x[n].bytes); + } + + acc = invert(acc); + + rct::key tmp; + for (int i = x.size(); i-- > 0; ) + { + sc_mul(tmp.bytes, acc.bytes, x[i].bytes); + sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes); + acc = tmp; + } + + return x; + } + + // Compute the slice of a vector + static epee::span<const rct::key> slice(const rct::keyV &a, size_t start, size_t stop) + { + CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index"); + CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index"); + CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices"); + return epee::span<const rct::key>(&a[start], stop - start); + } + + // Update the transcript + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0) + { + rct::key data[2]; + data[0] = transcript; + data[1] = update_0; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0, const rct::key &update_1) + { + rct::key data[3]; + data[0] = transcript; + data[1] = update_0; + data[2] = update_1; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + // Given a value v [0..2**N) and a mask gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma) + { + return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); + } + + BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma) + { + return bulletproof_plus_PROVE(std::vector<uint64_t>(1, v), rct::keyV(1, gamma)); + } + + // Given a set of values v [0..2**N) and masks gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma) + { + // Sanity check on inputs + CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma"); + CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty"); + for (const rct::key &sve: sv) + CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input"); + for (const rct::key &g: gamma) + CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input"); + + init_exponents(); + + // Useful proof bounds + // + // N: number of bits in each range (here, 64) + // logN: base-2 logarithm + // M: first power of 2 greater than or equal to the number of range proofs to aggregate + // logM: base-2 logarithm + constexpr size_t logN = 6; // log2(64) + constexpr size_t N = 1<<logN; + size_t M, logM; + for (logM = 0; (M = 1<<logM) <= maxM && M < sv.size(); ++logM); + CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv/gamma are too large"); + const size_t logMN = logM + logN; + const size_t MN = M * N; + + rct::keyV V(sv.size()); + rct::keyV aL(MN), aR(MN); + rct::keyV aL8(MN), aR8(MN); + rct::key temp; + rct::key temp2; + + // Prepare output commitments and offset by a factor of 8**(-1) + // + // This offset is applied to other group elements as well; + // it allows us to apply a multiply-by-8 operation in the verifier efficiently + // to ensure that the resulting group elements are in the prime-order point subgroup + // and avoid much more constly multiply-by-group-order operations. + for (size_t i = 0; i < sv.size(); ++i) + { + rct::key gamma8, sv8; + sc_mul(gamma8.bytes, gamma[i].bytes, INV_EIGHT.bytes); + sc_mul(sv8.bytes, sv[i].bytes, INV_EIGHT.bytes); + rct::addKeys2(V[i], gamma8, sv8, rct::H); + } + + // Decompose values + // + // Note that this effectively pads the set to a power of 2, which is required for the inner-product argument later. + for (size_t j = 0; j < M; ++j) + { + for (size_t i = N; i-- > 0; ) + { + if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8)))) + { + aL[j*N+i] = rct::identity(); + aL8[j*N+i] = INV_EIGHT; + aR[j*N+i] = aR8[j*N+i] = rct::zero(); + } + else + { + aL[j*N+i] = aL8[j*N+i] = rct::zero(); + aR[j*N+i] = MINUS_ONE; + aR8[j*N+i] = MINUS_INV_EIGHT; + } + } + } + +try_again: + // This is a Fiat-Shamir transcript + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(V)); + + // A + rct::key alpha = rct::skGen(); + rct::key pre_A = vector_exponent(aL8, aR8); + rct::key A; + sc_mul(temp.bytes, alpha.bytes, INV_EIGHT.bytes); + rct::addKeys(A, pre_A, rct::scalarmultBase(temp)); + + // Challenges + rct::key y = transcript_update(transcript, A); + if (y == rct::zero()) + { + MINFO("y is 0, trying again"); + goto try_again; + } + rct::key z = transcript = rct::hash_to_scalar(y); + if (z == rct::zero()) + { + MINFO("z is 0, trying again"); + goto try_again; + } + rct::key z_squared; + sc_mul(z_squared.bytes, z.bytes, z.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + // + // We compute this iteratively in order to reduce scalar operations. + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_mul(d[i].bytes, d[i-1].bytes, TWO.bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + rct::keyV y_powers = vector_of_scalar_powers(y, MN+2); + + // Prepare inner product terms + rct::keyV aL1 = vector_subtract(aL, z); + + rct::keyV aR1 = vector_add(aR, z); + rct::keyV d_y(MN); + for (size_t i = 0; i < MN; i++) + { + sc_mul(d_y[i].bytes, d[i].bytes, y_powers[MN-i].bytes); + } + aR1 = vector_add(aR1, d_y); + + rct::key alpha1 = alpha; + temp = ONE; + for (size_t j = 0; j < sv.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes); + sc_muladd(alpha1.bytes, temp2.bytes, gamma[j].bytes, alpha1.bytes); + } + + // These are used in the inner product rounds + size_t nprime = MN; + std::vector<ge_p3> Gprime(MN); + std::vector<ge_p3> Hprime(MN); + rct::keyV aprime(MN); + rct::keyV bprime(MN); + + const rct::key yinv = invert(y); + rct::keyV yinvpow(MN); + yinvpow[0] = ONE; + for (size_t i = 0; i < MN; ++i) + { + Gprime[i] = Gi_p3[i]; + Hprime[i] = Hi_p3[i]; + if (i > 0) + { + sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes); + } + aprime[i] = aL1[i]; + bprime[i] = aR1[i]; + } + rct::keyV L(logMN); + rct::keyV R(logMN); + int round = 0; + + // Inner-product rounds + while (nprime > 1) + { + nprime /= 2; + + rct::key cL = weighted_inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()), y); + rct::key cR = weighted_inner_product(vector_scalar(slice(aprime, nprime, aprime.size()), y_powers[nprime]), slice(bprime, 0, nprime), y); + + rct::key dL = rct::skGen(); + rct::key dR = rct::skGen(); + + L[round] = compute_LR(nprime, yinvpow[nprime], Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, cL, dL); + R[round] = compute_LR(nprime, y_powers[nprime], Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, cR, dR); + + const rct::key challenge = transcript_update(transcript, L[round], R[round]); + if (challenge == rct::zero()) + { + MINFO("challenge is 0, trying again"); + goto try_again; + } + + const rct::key challenge_inv = invert(challenge); + + sc_mul(temp.bytes, yinvpow[nprime].bytes, challenge.bytes); + hadamard_fold(Gprime, challenge_inv, temp); + hadamard_fold(Hprime, challenge, challenge_inv); + + sc_mul(temp.bytes, challenge_inv.bytes, y_powers[nprime].bytes); + aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), challenge), vector_scalar(slice(aprime, nprime, aprime.size()), temp)); + bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), challenge_inv), vector_scalar(slice(bprime, nprime, bprime.size()), challenge)); + + rct::key challenge_squared; + sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes); + rct::key challenge_squared_inv; + sc_mul(challenge_squared_inv.bytes, challenge_inv.bytes, challenge_inv.bytes); + sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes); + sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes); + + ++round; + } + + // Final round computations + rct::key r = rct::skGen(); + rct::key s = rct::skGen(); + rct::key d_ = rct::skGen(); + rct::key eta = rct::skGen(); + + std::vector<MultiexpData> A1_data; + A1_data.reserve(4); + A1_data.resize(4); + + sc_mul(A1_data[0].scalar.bytes, r.bytes, INV_EIGHT.bytes); + A1_data[0].point = Gprime[0]; + + sc_mul(A1_data[1].scalar.bytes, s.bytes, INV_EIGHT.bytes); + A1_data[1].point = Hprime[0]; + + sc_mul(A1_data[2].scalar.bytes, d_.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + A1_data[2].point = G_p3; + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, bprime[0].bytes); + sc_mul(temp2.bytes, s.bytes, y.bytes); + sc_mul(temp2.bytes, temp2.bytes, aprime[0].bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(A1_data[3].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + A1_data[3].point = H_p3; + + rct::key A1 = multiexp(A1_data, 0); + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, s.bytes); + sc_mul(temp.bytes, temp.bytes, INV_EIGHT.bytes); + sc_mul(temp2.bytes, eta.bytes, INV_EIGHT.bytes); + rct::key B; + rct::addKeys2(B, temp2, temp, rct::H); + + rct::key e = transcript_update(transcript, A1, B); + if (e == rct::zero()) + { + MINFO("e is 0, trying again"); + goto try_again; + } + rct::key e_squared; + sc_mul(e_squared.bytes, e.bytes, e.bytes); + + rct::key r1; + sc_muladd(r1.bytes, aprime[0].bytes, e.bytes, r.bytes); + + rct::key s1; + sc_muladd(s1.bytes, bprime[0].bytes, e.bytes, s.bytes); + + rct::key d1; + sc_muladd(d1.bytes, d_.bytes, e.bytes, eta.bytes); + sc_muladd(d1.bytes, alpha1.bytes, e_squared.bytes, d1.bytes); + + return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R)); + } + + BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma) + { + CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma"); + + // vG + gammaH + rct::keyV sv(v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + sv[i] = rct::d2h(v[i]); + } + return bulletproof_plus_PROVE(sv, gamma); + } + + struct bp_plus_proof_data_t + { + rct::key y, z, e; + std::vector<rct::key> challenges; + size_t logM, inv_offset; + }; + + // Given a batch of range proofs, determine if they are all valid + bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs) + { + init_exponents(); + + const size_t logN = 6; + const size_t N = 1 << logN; + + // Set up + size_t max_length = 0; // size of each of the longest proof's inner-product vectors + size_t nV = 0; // number of output commitments across all proofs + size_t inv_offset = 0; + size_t max_logM = 0; + + std::vector<bp_plus_proof_data_t> proof_data; + proof_data.reserve(proofs.size()); + + // We'll perform only a single batch inversion across all proofs in the batch, + // since batch inversion requires only one scalar inversion operation. + std::vector<rct::key> to_invert; + to_invert.reserve(11 * proofs.size()); // maximal size, given the aggregation limit + + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + + // Sanity checks + CHECK_AND_ASSERT_MES(is_reduced(proof.r1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.s1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.d1), false, "Input scalar not in range"); + + CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element"); + CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes"); + CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof"); + + max_length = std::max(max_length, proof.L.size()); + nV += proof.V.size(); + + proof_data.push_back({}); + bp_plus_proof_data_t &pd = proof_data.back(); + + // Reconstruct the challenges + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(proof.V)); + pd.y = transcript_update(transcript, proof.A); + CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0"); + pd.z = transcript = rct::hash_to_scalar(pd.y); + CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0"); + + // Determine the number of inner-product rounds based on proof size + size_t M; + for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < proof.V.size(); ++pd.logM); + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + max_logM = std::max(pd.logM, max_logM); + + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + // The inner-product challenges are computed per round + pd.challenges.resize(rounds); + for (size_t j = 0; j < rounds; ++j) + { + pd.challenges[j] = transcript_update(transcript, proof.L[j], proof.R[j]); + CHECK_AND_ASSERT_MES(!(pd.challenges[j] == rct::zero()), false, "challenges[j] == 0"); + } + + // Final challenge + pd.e = transcript_update(transcript,proof.A1,proof.B); + CHECK_AND_ASSERT_MES(!(pd.e == rct::zero()), false, "e == 0"); + + // Batch scalar inversions + pd.inv_offset = inv_offset; + for (size_t j = 0; j < rounds; ++j) + to_invert.push_back(pd.challenges[j]); + to_invert.push_back(pd.y); + inv_offset += rounds + 1; + } + CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large"); + size_t maxMN = 1u << max_length; + + rct::key temp; + rct::key temp2; + + // Final batch proof data + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN); + multiexp_data.resize(2 * maxMN); + + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); + + // Weights and aggregates + // + // The idea is to take the single multiscalar multiplication used in the verification + // of each proof in the batch and weight it using a random weighting factor, resulting + // in just one multiscalar multiplication check to zero for the entire batch. + // We can further simplify the verifier complexity by including common group elements + // only once in this single multiscalar multiplication. + // Common group elements' weighted scalar sums are tracked across proofs for this reason. + // + // To build a multiscalar multiplication for each proof, we use the method described in + // Section 6.1 of the preprint. Note that the result given there does not account for + // the construction of the inner-product inputs that are produced in the range proof + // verifier algorithm; we have done so here. + rct::key G_scalar = rct::zero(); + rct::key H_scalar = rct::zero(); + rct::keyV Gi_scalars(maxMN, rct::zero()); + rct::keyV Hi_scalars(maxMN, rct::zero()); + + int proof_data_index = 0; + rct::keyV challenges_cache; + std::vector<ge_p3> proof8_V, proof8_L, proof8_R; + + // Process each proof and add to the weighted batch + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + const bp_plus_proof_data_t &pd = proof_data[proof_data_index++]; + + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + const size_t M = 1 << pd.logM; + const size_t MN = M*N; + + // Random weighting factor must be nonzero, which is exceptionally unlikely! + rct::key weight = ZERO; + while (weight == ZERO) + { + weight = rct::skGen(); + } + + // Rescale previously offset proof elements + // + // This ensures that all such group elements are in the prime-order subgroup. + proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]); + proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]); + proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]); + ge_p3 proof8_A1; + ge_p3 proof8_B; + ge_p3 proof8_A; + rct::scalarmult8(proof8_A1, proof.A1); + rct::scalarmult8(proof8_B, proof.B); + rct::scalarmult8(proof8_A, proof.A); + + // Compute necessary powers of the y-challenge + rct::key y_MN = copy(pd.y); + rct::key y_MN_1; + size_t temp_MN = MN; + while (temp_MN > 1) + { + sc_mul(y_MN.bytes, y_MN.bytes, y_MN.bytes); + temp_MN /= 2; + } + sc_mul(y_MN_1.bytes, y_MN.bytes, pd.y.bytes); + + // V_j: -e**2 * z**(2*j+1) * y**(MN+1) * weight + rct::key e_squared; + sc_mul(e_squared.bytes, pd.e.bytes, pd.e.bytes); + + rct::key z_squared; + sc_mul(z_squared.bytes, pd.z.bytes, pd.z.bytes); + + sc_sub(temp.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(temp.bytes, temp.bytes, y_MN_1.bytes); + sc_mul(temp.bytes, temp.bytes, weight.bytes); + for (size_t j = 0; j < proof8_V.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + multiexp_data.emplace_back(temp, proof8_V[j]); + } + + // B: -weight + sc_mul(temp.bytes, MINUS_ONE.bytes, weight.bytes); + multiexp_data.emplace_back(temp, proof8_B); + + // A1: -weight*e + sc_mul(temp.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(temp, proof8_A1); + + // A: -weight*e*e + rct::key minus_weight_e_squared; + sc_mul(minus_weight_e_squared.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(minus_weight_e_squared, proof8_A); + + // G: weight*d1 + sc_muladd(G_scalar.bytes, weight.bytes, proof.d1.bytes, G_scalar.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_add(d[i].bytes, d[i-1].bytes, d[i-1].bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + // More efficient computation of sum(d) + rct::key sum_d; + sc_mul(sum_d.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, sum_of_even_powers(pd.z, 2*M).bytes); + + // H: weight*( r1*y*s1 + e**2*( y**(MN+1)*z*sum(d) + (z**2-z)*sum(y) ) ) + rct::key sum_y = sum_of_scalar_powers(pd.y, MN); + sc_sub(temp.bytes, z_squared.bytes, pd.z.bytes); + sc_mul(temp.bytes, temp.bytes, sum_y.bytes); + + sc_mul(temp2.bytes, y_MN_1.bytes, pd.z.bytes); + sc_mul(temp2.bytes, temp2.bytes, sum_d.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(temp.bytes, temp.bytes, e_squared.bytes); + sc_mul(temp2.bytes, proof.r1.bytes, pd.y.bytes); + sc_mul(temp2.bytes, temp2.bytes, proof.s1.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_muladd(H_scalar.bytes, temp.bytes, weight.bytes, H_scalar.bytes); + + // Compute the number of rounds for the inner-product argument + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + const rct::key *challenges_inv = &inverses[pd.inv_offset]; + const rct::key yinv = inverses[pd.inv_offset + rounds]; + + // Compute challenge products + challenges_cache.resize(1<<rounds); + challenges_cache[0] = challenges_inv[0]; + challenges_cache[1] = pd.challenges[0]; + for (size_t j = 1; j < rounds; ++j) + { + const size_t slots = 1<<(j+1); + for (size_t s = slots; s-- > 0; --s) + { + sc_mul(challenges_cache[s].bytes, challenges_cache[s/2].bytes, pd.challenges[j].bytes); + sc_mul(challenges_cache[s-1].bytes, challenges_cache[s/2].bytes, challenges_inv[j].bytes); + } + } + + // Gi and Hi + rct::key e_r1_w_y; + sc_mul(e_r1_w_y.bytes, pd.e.bytes, proof.r1.bytes); + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, weight.bytes); + rct::key e_s1_w; + sc_mul(e_s1_w.bytes, pd.e.bytes, proof.s1.bytes); + sc_mul(e_s1_w.bytes, e_s1_w.bytes, weight.bytes); + rct::key e_squared_z_w; + sc_mul(e_squared_z_w.bytes, e_squared.bytes, pd.z.bytes); + sc_mul(e_squared_z_w.bytes, e_squared_z_w.bytes, weight.bytes); + rct::key minus_e_squared_z_w; + sc_sub(minus_e_squared_z_w.bytes, ZERO.bytes, e_squared_z_w.bytes); + rct::key minus_e_squared_w_y; + sc_sub(minus_e_squared_w_y.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, weight.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, y_MN.bytes); + for (size_t i = 0; i < MN; ++i) + { + rct::key g_scalar = copy(e_r1_w_y); + rct::key h_scalar; + + // Use the binary decomposition of the index + sc_muladd(g_scalar.bytes, g_scalar.bytes, challenges_cache[i].bytes, e_squared_z_w.bytes); + sc_muladd(h_scalar.bytes, e_s1_w.bytes, challenges_cache[(~i) & (MN-1)].bytes, minus_e_squared_z_w.bytes); + + // Complete the scalar derivation + sc_add(Gi_scalars[i].bytes, Gi_scalars[i].bytes, g_scalar.bytes); + sc_muladd(h_scalar.bytes, minus_e_squared_w_y.bytes, d[i].bytes, h_scalar.bytes); + sc_add(Hi_scalars[i].bytes, Hi_scalars[i].bytes, h_scalar.bytes); + + // Update iterated values + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, yinv.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, yinv.bytes); + } + + // L_j: -weight*e*e*challenges[j]**2 + // R_j: -weight*e*e*challenges[j]**(-2) + for (size_t j = 0; j < rounds; ++j) + { + sc_mul(temp.bytes, pd.challenges[j].bytes, pd.challenges[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_L[j]); + + sc_mul(temp.bytes, challenges_inv[j].bytes, challenges_inv[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_R[j]); + } + } + + // Verify all proofs in the weighted batch + multiexp_data.emplace_back(G_scalar, rct::G); + multiexp_data.emplace_back(H_scalar, rct::H); + for (size_t i = 0; i < maxMN; ++i) + { + multiexp_data[i * 2] = {Gi_scalars[i], Gi_p3[i]}; + multiexp_data[i * 2 + 1] = {Hi_scalars[i], Hi_p3[i]}; + } + if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity())) + { + MERROR("Verification failure"); + return false; + } + + return true; + } + + bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs) + { + std::vector<const BulletproofPlus*> proof_pointers; + proof_pointers.reserve(proofs.size()); + for (const BulletproofPlus &proof: proofs) + proof_pointers.push_back(&proof); + return bulletproof_plus_VERIFY(proof_pointers); + } + + bool bulletproof_plus_VERIFY(const BulletproofPlus &proof) + { + std::vector<const BulletproofPlus*> proofs; + proofs.push_back(&proof); + return bulletproof_plus_VERIFY(proofs); + } +} diff --git a/src/p2p/stdafx.h b/src/ringct/bulletproofs_plus.h index a689b1c15..861c54f4f 100644 --- a/src/p2p/stdafx.h +++ b/src/ringct/bulletproofs_plus.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -25,29 +25,25 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include "targetver.h" - - -#if !defined(__GNUC__) -#define _CRTDBG_MAP_ALLOC -#include <stdlib.h> -#include <crtdbg.h> -#endif - - - -#include <stdio.h> +#ifndef BULLETPROOFS_PLUS_H +#define BULLETPROOFS_PLUS_H +#include "rctTypes.h" -#define BOOST_FILESYSTEM_VERSION 3 -#define ENABLE_RELEASE_LOGGING -#include "log_opt_defs.h" -#include "misc_log_ex.h" +namespace rct +{ +BulletproofPlus bulletproof_plus_PROVE(const rct::key &v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &v, const rct::keyV &gamma); +BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma); +bool bulletproof_plus_VERIFY(const BulletproofPlus &proof); +bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs); +bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs); +} +#endif diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index 784c90a4e..67814682a 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -1,4 +1,5 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2017-2022, The Monero Project + // // All rights reserved. // @@ -235,7 +236,7 @@ rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data) heap.reserve(points); for (size_t n = 0; n < points; ++n) { - if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity(&data[n].point)) + if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity_vartime(&data[n].point)) heap.push_back(n); } points = heap.size(); @@ -457,7 +458,7 @@ rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<str MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000)); std::vector<uint8_t> skip(data.size()); for (size_t i = 0; i < data.size(); ++i) - skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity(&data[i].point); + skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity_vartime(&data[i].point); MULTIEXP_PERF(PERF_TIMER_STOP(skip)); #endif diff --git a/src/ringct/multiexp.h b/src/ringct/multiexp.h index b52707933..788516c0b 100644 --- a/src/ringct/multiexp.h +++ b/src/ringct/multiexp.h @@ -1,4 +1,5 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2017-2022, The Monero Project + // // All rights reserved. // diff --git a/src/ringct/rctCryptoOps.c b/src/ringct/rctCryptoOps.c index 506f85c16..a2bb68665 100644 --- a/src/ringct/rctCryptoOps.c +++ b/src/ringct/rctCryptoOps.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctCryptoOps.h b/src/ringct/rctCryptoOps.h index dabb2606a..2d6e13bb7 100644 --- a/src/ringct/rctCryptoOps.h +++ b/src/ringct/rctCryptoOps.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f5950c53c..21040317c 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -35,6 +35,7 @@ #include "common/util.h" #include "rctSigs.h" #include "bulletproofs.h" +#include "bulletproofs_plus.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_config.h" @@ -78,6 +79,44 @@ namespace return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I}; } + + rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks) + { + const size_t n_outs = outamounts.size(); + const rct::key I = rct::identity(); + size_t nrl = 0; + while ((1u << nrl) < n_outs) + ++nrl; + nrl += 6; + + C.resize(n_outs); + masks.resize(n_outs); + for (size_t i = 0; i < n_outs; ++i) + { + masks[i] = I; + rct::key sv8, sv; + sv = rct::zero(); + sv.bytes[0] = outamounts[i] & 255; + sv.bytes[1] = (outamounts[i] >> 8) & 255; + sv.bytes[2] = (outamounts[i] >> 16) & 255; + sv.bytes[3] = (outamounts[i] >> 24) & 255; + sv.bytes[4] = (outamounts[i] >> 32) & 255; + sv.bytes[5] = (outamounts[i] >> 40) & 255; + sv.bytes[6] = (outamounts[i] >> 48) & 255; + sv.bytes[7] = (outamounts[i] >> 56) & 255; + sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); + rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); + } + + return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)}; + } + + rct::clsag make_dummy_clsag(size_t ring_size) + { + const rct::key I = rct::identity(); + const size_t n_scalars = ring_size; + return rct::clsag{rct::keyV(n_scalars, I), I, I, I}; + } } namespace rct { @@ -107,6 +146,32 @@ namespace rct { catch (...) { return false; } } + BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev) + { + CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + masks.resize(amounts.size()); + for (size_t i = 0; i < masks.size(); ++i) + masks[i] = hwdev.genCommitmentMask(sk[i]); + BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks); + CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); + C = proof.V; + return proof; + } + + bool verBulletproofPlus(const BulletproofPlus &proof) + { + try { return bulletproof_plus_VERIFY(proof); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + + bool verBulletproofPlus(const std::vector<const BulletproofPlus*> &proofs) + { + try { return bulletproof_plus_VERIFY(proofs); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + //Borromean (c.f. gmax/andytoshi's paper) boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { key64 L[2], alpha; @@ -173,14 +238,12 @@ namespace rct { // P[l] == p*G // C[l] == z*G // C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev) { + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev) { clsag sig; size_t n = P.size(); // ring size CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present"); // Key images ge_p3 H_p3; @@ -195,16 +258,7 @@ namespace rct { key aG; key aH; - // Multisig - if (kLRki) - { - sig.I = kLRki->ki; - scalarmultKey(D,H,z); - } - else - { - hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); - } + hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); geDsmp I_precomp; geDsmp D_precomp; @@ -252,18 +306,9 @@ namespace rct { c_to_hash[2*n+1] = C_offset; c_to_hash[2*n+2] = message; - // Multisig data is present - if (kLRki) - { - a = kLRki->k; - c_to_hash[2*n+3] = kLRki->L; - c_to_hash[2*n+4] = kLRki->R; - } - else - { - c_to_hash[2*n+3] = aG; - c_to_hash[2*n+4] = aH; - } + c_to_hash[2*n+3] = aG; + c_to_hash[2*n+4] = aH; + hwdev.clsag_hash(c_to_hash,c); size_t i; @@ -315,16 +360,11 @@ namespace rct { hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]); memwipe(&a, sizeof(key)); - if (mscout) - *mscout = c; - if (mspout) - *mspout = mu_P; - return sig; } clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) { - return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL, hw::get_device("default")); + return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, hw::get_device("default")); } // MLSAG signatures @@ -332,7 +372,7 @@ namespace rct { // This generalization allows for some dimensions not to require linkability; // this is used in practice for commitment data within signatures // Note that using more than one linkable dimension is not recommended. - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev) { + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -344,8 +384,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size"); CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES(!kLRki || dsRows == 1, "Multisig requires exactly 1 dsRows"); size_t i = 0, j = 0, ii = 0; key c, c_old, L, R, Hi; @@ -363,20 +401,11 @@ namespace rct { DP("here1"); for (i = 0; i < dsRows; i++) { toHash[3 * i + 1] = pk[index][i]; - if (kLRki) { - // multisig - alpha[i] = kLRki->k; - toHash[3 * i + 2] = kLRki->L; - toHash[3 * i + 3] = kLRki->R; - rv.II[i] = kLRki->ki; - } - else { - hash_to_p3(Hi_p3, pk[index][i]); - ge_p3_tobytes(Hi.bytes, &Hi_p3); - hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); - toHash[3 * i + 2] = aG[i]; - toHash[3 * i + 3] = aHP[i]; - } + hash_to_p3(Hi_p3, pk[index][i]); + ge_p3_tobytes(Hi.bytes, &Hi_p3); + hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; precomp(Ip[i].k, rv.II[i]); } size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) @@ -420,8 +449,6 @@ namespace rct { } } hwdev.mlsag_sign(c, xx, alpha, rows, dsRows, rv.ss[index]); - if (mscout) - *mscout = c; return rv; } @@ -611,6 +638,25 @@ namespace rct { kv.push_back(p.t); } } + else if (rv.type == RCTTypeBulletproofPlus) + { + kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size()); + for (const auto &p: rv.p.bulletproofs_plus) + { + // V are not hashed as they're expanded from outPk.mask + // (and thus hashed as part of rctSigBase above) + kv.push_back(p.A); + kv.push_back(p.A1); + kv.push_back(p.B); + kv.push_back(p.r1); + kv.push_back(p.s1); + kv.push_back(p.d1); + for (size_t n = 0; n < p.L.size(); ++n) + kv.push_back(p.L[n]); + for (size_t n = 0; n < p.R.size(); ++n) + kv.push_back(p.R[n]); + } + } else { kv.reserve((64*3+1) * rv.p.rangeSigs.size()); @@ -638,7 +684,7 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { + mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); @@ -649,7 +695,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size"); CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV sk(rows + 1); keyV tmp(rows + 1); @@ -682,7 +727,7 @@ namespace rct { for (size_t j = 0; j < outPk.size(); j++) { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -695,12 +740,11 @@ namespace rct { // inSk is x, a_in corresponding to signing index // a_out, Cout is for the output commitment // index is the signing index.. - mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev) { + mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); size_t i; @@ -712,17 +756,16 @@ namespace rct { M[i][0] = pubs[i].dest; subKeys(M[i][1], pubs[i].mask, Cout); } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } - clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, unsigned int index, hw::device &hwdev) { + clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); keyM M(cols, tmp); @@ -742,7 +785,7 @@ namespace rct { sk[0] = copy(inSk.dest); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); - clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout, hwdev); + clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -1000,14 +1043,13 @@ namespace rct { // must know the destination private key to find the correct amount, else will return a random number // Note: For txn fees, the last index in the amounts vector should contain that // Thus the amounts vector will be "one" longer than the destinations vectort - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings"); rctSig rv; @@ -1031,7 +1073,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1046,24 +1088,22 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - if (msout) - msout->c.resize(1); - rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey,hwdev)); + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey,hwdev)); return rv; } - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { unsigned int index; ctkeyM mixRing; ctkeyV outSk; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RCT simple //for post-rct only - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { - const bool bulletproof = rct_config.range_proof_type != RangeProofBorromean; + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -1073,17 +1113,16 @@ namespace rct { for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); - if (kLRki && msout) { - CHECK_AND_ASSERT_THROW_MES(kLRki->size() == inamounts.size(), "Mismatched kLRki/inamounts sizes"); - } rctSig rv; - if (bulletproof) + if (bulletproof_or_plus) { switch (rct_config.bp_version) { case 0: + case 4: + rv.type = RCTTypeBulletproofPlus; + break; case 3: rv.type = RCTTypeCLSAG; break; @@ -1102,7 +1141,7 @@ namespace rct { rv.message = message; rv.outPk.resize(destinations.size()); - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); @@ -1114,17 +1153,19 @@ namespace rct { //add destination to sig rv.outPk[i].dest = copy(destinations[i]); //compute range proof - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG - if (!bulletproof) + if (!bulletproof_or_plus) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif } rv.p.bulletproofs.clear(); - if (bulletproof) + rv.p.bulletproofs_plus.clear(); + if (bulletproof_or_plus) { + const bool plus = is_rct_bulletproof_plus(rv.type); size_t n_amounts = outamounts.size(); size_t amounts_proved = 0; if (rct_config.range_proof_type == RangeProofPaddedBulletproof) @@ -1133,14 +1174,23 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[0], amount_keys.size()}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < outamounts.size(); ++i) @@ -1153,7 +1203,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofMultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? BULLETPROOF_PLUS_MAX_OUTPUTS : BULLETPROOF_MAX_OUTPUTS)) batch_size *= 2; rct::keyV C, masks; std::vector<uint64_t> batch_amounts(batch_size); @@ -1162,14 +1212,23 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < batch_size; ++i) @@ -1189,7 +1248,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(outamounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1197,9 +1256,9 @@ namespace rct { // TODO: unused ?? // key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts; pseudoOuts.resize(inamounts.size()); - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) rv.p.CLSAGs.resize(inamounts.size()); else rv.p.MGs.resize(inamounts.size()); @@ -1215,26 +1274,25 @@ namespace rct { DP(pseudoOuts[i]); key full_message = get_pre_mlsag_hash(rv,hwdev); - if (msout) - { - msout->c.resize(inamounts.size()); - msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0); - } + for (i = 0 ; i < inamounts.size(); i++) { - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) { - rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev); + if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) + rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size()); + else + rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } else { - rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev); + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } } return rv; } - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { std::vector<unsigned int> index; index.resize(inPk.size()); ctkeyM mixRing; @@ -1244,7 +1302,7 @@ namespace rct { mixRing[i].resize(mixin+1); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); } - return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RingCT protocol @@ -1328,20 +1386,25 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results; - std::vector<const Bulletproof*> proofs; + std::vector<const Bulletproof*> bp_proofs; + std::vector<const BulletproofPlus*> bpp_proofs; size_t max_non_bp_proofs = 0, offset = 0; for (const rctSig *rvp: rvv) { CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); const rctSig &rv = *rvp; - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + if (bulletproof || bulletproof_plus) { - CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); - if (rv.type == RCTTypeCLSAG) + if (bulletproof_plus) + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus"); + else + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); + if (is_rct_clsag(rv.type)) { CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG"); CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs"); @@ -1361,7 +1424,7 @@ namespace rct { } CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - if (!bulletproof) + if (!bulletproof && !bulletproof_plus) max_non_bp_proofs += rv.p.rangeSigs.size(); } @@ -1371,7 +1434,8 @@ namespace rct { const rctSig &rv = *rvp; const bool bulletproof = is_rct_bulletproof(rv.type); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { @@ -1391,10 +1455,15 @@ namespace rct { return false; } - if (bulletproof) + if (bulletproof_plus) + { + for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++) + bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]); + } + else if (bulletproof) { for (size_t i = 0; i < rv.p.bulletproofs.size(); i++) - proofs.push_back(&rv.p.bulletproofs[i]); + bp_proofs.push_back(&rv.p.bulletproofs[i]); } else { @@ -1403,9 +1472,18 @@ namespace rct { offset += rv.p.rangeSigs.size(); } } - if (!proofs.empty() && !verBulletproof(proofs)) + if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) + { + LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; + return false; + } + if (!bp_proofs.empty() && !verBulletproof(bp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; return false; } @@ -1445,11 +1523,12 @@ namespace rct { { PERF_TIMER(verRctNonSemanticsSimple); - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet - if (bulletproof) + if (bulletproof || bulletproof_plus) CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing"); else CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); @@ -1460,7 +1539,7 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; const key message = get_pre_mlsag_hash(rv, hw::get_device("default")); @@ -1468,10 +1547,8 @@ namespace rct { results.resize(rv.mixRing.size()); for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { tpool.submit(&waiter, [&, i] { - if (rv.type == RCTTypeCLSAG) - { + if (is_rct_clsag(rv.type)) results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]); - } else results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]); }); @@ -1518,7 +1595,7 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; @@ -1542,13 +1619,14 @@ namespace rct { } xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, + false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; @@ -1570,59 +1648,4 @@ namespace rct { key mask; return decodeRctSimple(rv, sk, i, mask, hwdev); } - - bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, - false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs"); - if (rv.type == RCTTypeFull) - { - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element"); - } - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.MGs[n].ss.size(), false, "Index out of range"); - CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line"); - } - - // MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share - // cc: msout.c[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff; - sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes); - sc_add(rv.p.MGs[n].ss[indices[n]][0].bytes, rv.p.MGs[n].ss[indices[n]][0].bytes, diff.bytes); - } - return true; - } - - bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeCLSAG, false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs"); - CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size"); - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range"); - } - - // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share - // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff, sk; - sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes); - sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes); - sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes); - } - return true; - } - - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - if (rv.type == RCTTypeCLSAG) - return signMultisigCLSAG(rv, indices, k, msout, secret_key); - else - return signMultisigMLSAG(rv, indices, k, msout, secret_key); - } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index a0346b34e..17cfd77b9 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -74,12 +74,12 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev); + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev); + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev); clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l); - clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &); + clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, unsigned int, hw::device &); bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &); //proveRange and verRange @@ -100,8 +100,8 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); - mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); + mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, const key &txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); @@ -123,10 +123,10 @@ namespace rct { //decodeRct: (c.f. https://eprint.iacr.org/2015/1098 section 5.1.1) // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSemanticsSimple(const rctSig & rv); @@ -138,7 +138,6 @@ namespace rct { xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev); key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev); - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key); } #endif /* RCTSIGS_H */ diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 1f674056d..c22b0524f 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -196,6 +196,7 @@ namespace rct { case RCTTypeBulletproof: case RCTTypeBulletproof2: case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: return true; default: return false; @@ -215,6 +216,17 @@ namespace rct { } } + bool is_rct_bulletproof_plus(int type) + { + switch (type) + { + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + bool is_rct_borromean(int type) { switch (type) @@ -227,19 +239,34 @@ namespace rct { } } - size_t n_bulletproof_amounts(const Bulletproof &proof) + bool is_rct_clsag(int type) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + switch (type) + { + case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + + static size_t n_bulletproof_amounts_base(const size_t L_size, const size_t R_size, const size_t V_size, const size_t max_outputs) + { + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() > 0, 0, "Empty bulletproof"); - return proof.V.size(); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(V_size <= (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size * 2 > (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size > 0, 0, "Empty bulletproof"); + return V_size; } + size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs) { size_t n = 0; @@ -254,15 +281,31 @@ namespace rct { return n; } - size_t n_bulletproof_max_amounts(const Bulletproof &proof) + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + + static size_t n_bulletproof_max_amounts_base(size_t L_size, size_t R_size, size_t max_outputs) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - return 1 << (proof.L.size() - 6); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + return 1 << (L_size - 6); } + size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs) { @@ -278,4 +321,18 @@ namespace rct { return n; } + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_max_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 278ff4164..59ed4d6a6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -238,11 +238,48 @@ namespace rct { END_SERIALIZE() }; + struct BulletproofPlus + { + rct::keyV V; + rct::key A, A1, B; + rct::key r1, s1, d1; + rct::keyV L, R; + + BulletproofPlus() {} + 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): + V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + + bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; } + + BEGIN_SERIALIZE_OBJECT() + // Commitments aren't saved, they're restored via outPk + // FIELD(V) + FIELD(A) + FIELD(A1) + FIELD(B) + FIELD(r1) + FIELD(s1) + FIELD(d1) + FIELD(L) + FIELD(R) + + if (L.empty() || L.size() != R.size()) + return false; + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs); size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs); + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs); + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs); + //A container to hold all signatures necessary for RingCT // rangeSigs holds all the rangeproof data of a transaction // MG holds the MLSAG signature of a transaction @@ -257,6 +294,7 @@ namespace rct { RCTTypeBulletproof = 3, RCTTypeBulletproof2 = 4, RCTTypeCLSAG = 5, + RCTTypeBulletproofPlus = 6, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -285,7 +323,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; VARINT_FIELD(txnFee) // inputs/outputs not saved, only here for serialization help @@ -314,7 +352,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.begin_object(); if (!typename Archive<W>::is_saving()) @@ -360,6 +398,7 @@ namespace rct { struct rctSigPrunable { std::vector<rangeSig> rangeSigs; std::vector<Bulletproof> bulletproofs; + std::vector<BulletproofPlus> bulletproofs_plus; std::vector<mgSig> MGs; // simple rct has N, full has 1 std::vector<clsag> CLSAGs; keyV pseudoOuts; //C - for simple rct @@ -376,9 +415,28 @@ namespace rct { return false; if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproofPlus) + { + uint32_t nbp = bulletproofs_plus.size(); + VARINT_FIELD(nbp) + ar.tag("bpp"); + ar.begin_array(); + if (nbp > outputs) + return false; + PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + for (size_t i = 0; i < nbp; ++i) + { + FIELDS(bulletproofs_plus[i]) + if (nbp - i > 1) + ar.delimit_array(); + } + if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) + return false; + ar.end_array(); + } + else if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { uint32_t nbp = bulletproofs.size(); if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) @@ -416,7 +474,7 @@ namespace rct { ar.end_array(); } - if (type == RCTTypeCLSAG) + if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("CLSAGs"); ar.begin_array(); @@ -507,7 +565,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -528,6 +586,7 @@ namespace rct { BEGIN_SERIALIZE_OBJECT() FIELD(rangeSigs) FIELD(bulletproofs) + FIELD(bulletproofs_plus) FIELD(MGs) FIELD(CLSAGs) FIELD(pseudoOuts) @@ -538,12 +597,12 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } BEGIN_SERIALIZE_OBJECT() @@ -655,7 +714,9 @@ namespace rct { bool is_rct_simple(int type); bool is_rct_bulletproof(int type); + bool is_rct_bulletproof_plus(int type); bool is_rct_borromean(int type); + bool is_rct_clsag(int type); static inline const rct::key &pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; } static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; } @@ -711,6 +772,7 @@ VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag"); +VARIANT_TAG(debug_archive, rct::BulletproofPlus, "rct::bulletproof_plus"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -728,6 +790,7 @@ VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(binary_archive, rct::clsag, 0x9f); +VARIANT_TAG(binary_archive, rct::BulletproofPlus, 0xa0); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -745,5 +808,6 @@ VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); VARIANT_TAG(json_archive, rct::clsag, "rct_clsag"); +VARIANT_TAG(json_archive, rct::BulletproofPlus, "rct_bulletproof_plus"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 15e433e10..edfc70067 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/rpc/bootstrap_node_selector.cpp b/src/rpc/bootstrap_node_selector.cpp index 34845060e..a1cad3e59 100644 --- a/src/rpc/bootstrap_node_selector.cpp +++ b/src/rpc/bootstrap_node_selector.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/rpc/bootstrap_node_selector.h b/src/rpc/bootstrap_node_selector.h index 47722a008..616c180fc 100644 --- a/src/rpc/bootstrap_node_selector.h +++ b/src/rpc/bootstrap_node_selector.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 942bfce0a..5304333ff 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -48,6 +48,7 @@ using namespace epee; #include "cryptonote_basic/merge_mining.h" #include "cryptonote_core/tx_sanity_check.h" #include "misc_language.h" +#include "net/local_ip.h" #include "net/parse.h" #include "storages/http_abstract_invoke.h" #include "crypto/hash.h" @@ -349,12 +350,23 @@ namespace cryptonote bool store_ssl_key = !restricted && rpc_config->ssl_options && rpc_config->ssl_options.auth.certificate_path.empty(); const auto ssl_base_path = (boost::filesystem::path{data_dir} / "rpc_ssl").string(); - if (store_ssl_key && boost::filesystem::exists(ssl_base_path + ".crt")) + const bool ssl_cert_file_exists = boost::filesystem::exists(ssl_base_path + ".crt"); + const bool ssl_pkey_file_exists = boost::filesystem::exists(ssl_base_path + ".key"); + if (store_ssl_key) { - // load key from previous run, password prompted by OpenSSL - store_ssl_key = false; - rpc_config->ssl_options.auth = - epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"}; + // .key files are often given different read permissions as their corresponding .crt files. + // Consequently, sometimes the .key file wont't get copied, while the .crt file will. + if (ssl_cert_file_exists != ssl_pkey_file_exists) + { + MFATAL("Certificate (.crt) and private key (.key) files must both exist or both not exist at path: " << ssl_base_path); + return false; + } + else if (ssl_cert_file_exists) { // and ssl_pkey_file_exists + // load key from previous run, password prompted by OpenSSL + store_ssl_key = false; + rpc_config->ssl_options.auth = + epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"}; + } } auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; @@ -364,6 +376,8 @@ namespace cryptonote std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); + m_net_server.get_config_object().m_max_content_length = MAX_RPC_CONTENT_LENGTH; + if (store_ssl_key && inited) { // new keys were generated, store for next run @@ -534,6 +548,7 @@ namespace cryptonote res.version = restricted ? "" : MONERO_VERSION_FULL; res.synchronized = check_core_ready(); res.busy_syncing = m_p2p.get_payload_object().is_busy_syncing(); + res.restricted = restricted; res.status = CORE_RPC_STATUS_OK; return true; @@ -972,14 +987,26 @@ namespace cryptonote LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); } - std::vector<std::string>::const_iterator txhi = req.txs_hashes.begin(); - std::vector<crypto::hash>::const_iterator vhi = vh.begin(); + CHECK_AND_ASSERT_MES(txs.size() + missed_txs.size() == vh.size(), false, "mismatched number of txs"); + + auto txhi = req.txs_hashes.cbegin(); + auto vhi = vh.cbegin(); + auto missedi = missed_txs.cbegin(); + for(auto& tx: txs) { res.txs.push_back(COMMAND_RPC_GET_TRANSACTIONS::entry()); COMMAND_RPC_GET_TRANSACTIONS::entry &e = res.txs.back(); + while (missedi != missed_txs.end() && *missedi == *vhi) + { + ++vhi; + ++txhi; + ++missedi; + } + crypto::hash tx_hash = *vhi++; + CHECK_AND_ASSERT_MES(tx_hash == std::get<0>(tx), false, "mismatched tx hash"); e.tx_hash = *txhi++; e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx)); if (req.split || req.prune || std::get<3>(tx).empty()) @@ -1900,6 +1927,43 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_calcpow(const COMMAND_RPC_CALCPOW::request& req, COMMAND_RPC_CALCPOW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(calcpow); + + blobdata blockblob; + if(!string_tools::parse_hexstr_to_binbuff(req.block_blob, blockblob)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Wrong block blob"; + return false; + } + if(!m_core.check_incoming_block_size(blockblob)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE; + error_resp.message = "Block blob size is too big, rejecting block"; + return false; + } + crypto::hash seed_hash, pow_hash; + std::string buf; + if(req.seed_hash.size()) + { + if (!string_tools::parse_hexstr_to_binbuff(req.seed_hash, buf) || + buf.size() != sizeof(crypto::hash)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Wrong seed hash"; + return false; + } + buf.copy(reinterpret_cast<char *>(&seed_hash), sizeof(crypto::hash)); + } + + cryptonote::get_block_longhash(&(m_core.get_blockchain_storage()), blockblob, pow_hash, req.height, + req.major_version, req.seed_hash.size() ? &seed_hash : NULL, 0); + res = string_tools::pod_to_hex(pow_hash); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(add_aux_pow); @@ -2820,7 +2884,17 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); - res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); + + const uint8_t version = m_core.get_blockchain_storage().get_current_hard_fork_version(); + if (version >= HF_VERSION_2021_SCALING) + { + m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.grace_blocks, res.fees); + res.fee = res.fees[0]; + } + else + { + res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); + } res.quantization_mask = Blockchain::get_fee_quantization_mask(); res.status = CORE_RPC_STATUS_OK; return true; @@ -2886,7 +2960,7 @@ namespace cryptonote { if (req.limit_down != -1) { - res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + res.status = "Invalid parameter"; return true; } epee::net_utils::connection_basic::set_rate_down_limit(nodetool::default_limit_down); @@ -2900,7 +2974,7 @@ namespace cryptonote { if (req.limit_up != -1) { - res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + res.status = "Invalid parameter"; return true; } epee::net_utils::connection_basic::set_rate_up_limit(nodetool::default_limit_up); @@ -3152,6 +3226,14 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::JON_RPC, "get_output_distribution", req, res, r)) return r; + const bool restricted = m_restricted && ctx; + if (restricted && req.amounts != std::vector<uint64_t>(1, 0)) + { + error_resp.code = CORE_RPC_ERROR_CODE_RESTRICTED; + error_resp.message = "Restricted RPC can only get output distribution for rct outputs. Use your own node."; + return false; + } + size_t n_0 = 0, n_non0 = 0; for (uint64_t amount: req.amounts) if (amount) ++n_non0; else ++n_0; @@ -3193,6 +3275,13 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::BIN, "/get_output_distribution.bin", req, res, r)) return r; + const bool restricted = m_restricted && ctx; + if (restricted && req.amounts != std::vector<uint64_t>(1, 0)) + { + res.status = "Restricted RPC can only get output distribution for rct outputs. Use your own node."; + return false; + } + size_t n_0 = 0, n_non0 = 0; for (uint64_t amount: req.amounts) if (amount) ++n_non0; else ++n_0; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 84b14383a..0274f4db8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -149,6 +149,7 @@ namespace cryptonote MAP_JON_RPC_WE("get_block_template", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("get_miner_data", on_getminerdata, COMMAND_RPC_GETMINERDATA) + MAP_JON_RPC_WE_IF("calc_pow", on_calcpow, COMMAND_RPC_CALCPOW, !m_restricted) MAP_JON_RPC_WE("add_aux_pow", on_add_aux_pow, COMMAND_RPC_ADD_AUX_POW) MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) @@ -231,6 +232,7 @@ namespace cryptonote bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_getminerdata(const COMMAND_RPC_GETMINERDATA::request& req, COMMAND_RPC_GETMINERDATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_calcpow(const COMMAND_RPC_CALCPOW::request& req, COMMAND_RPC_CALCPOW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::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 1dbfc83a7..1be2610ff 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 8 +#define CORE_RPC_VERSION_MINOR 10 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -689,6 +689,7 @@ namespace cryptonote bool busy_syncing; std::string version; bool synchronized; + bool restricted; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -730,6 +731,7 @@ namespace cryptonote KV_SERIALIZE(busy_syncing) KV_SERIALIZE(version) KV_SERIALIZE(synchronized) + KV_SERIALIZE(restricted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -990,6 +992,28 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_CALCPOW + { + struct request_t: public rpc_request_base + { + uint8_t major_version; + uint64_t height; + blobdata block_blob; + std::string seed_hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(major_version) + KV_SERIALIZE(height) + KV_SERIALIZE(block_blob) + KV_SERIALIZE(seed_hash) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + typedef std::string response; + }; + struct COMMAND_RPC_ADD_AUX_POW { struct aux_pow_t @@ -2167,11 +2191,13 @@ namespace cryptonote { uint64_t fee; uint64_t quantization_mask; + std::vector<uint64_t> fees; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(fee) KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) + KV_SERIALIZE(fees) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index 232df0373..1ba2392a9 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index cdfc363a9..0466c92c2 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_handler.h b/src/rpc/daemon_handler.h index 31c4f3ec4..74885cf30 100644 --- a/src/rpc/daemon_handler.h +++ b/src/rpc/daemon_handler.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp index 887486f77..a7c921ff9 100644 --- a/src/rpc/daemon_messages.cpp +++ b/src/rpc/daemon_messages.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h index dea37c9a6..fb33f0d4a 100644 --- a/src/rpc/daemon_messages.h +++ b/src/rpc/daemon_messages.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_rpc_version.h b/src/rpc/daemon_rpc_version.h index 2955d5449..62847fd58 100644 --- a/src/rpc/daemon_rpc_version.h +++ b/src/rpc/daemon_rpc_version.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/fwd.h b/src/rpc/fwd.h index 72537f5a5..cd9eacb9a 100644 --- a/src/rpc/fwd.h +++ b/src/rpc/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/instanciations.cpp b/src/rpc/instanciations.cpp index ac992d4ad..6e48d36a8 100644 --- a/src/rpc/instanciations.cpp +++ b/src/rpc/instanciations.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp index f6c6b887d..005c40ea2 100644 --- a/src/rpc/message.cpp +++ b/src/rpc/message.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message.h b/src/rpc/message.h index 04bf1a111..730b3ec74 100644 --- a/src/rpc/message.h +++ b/src/rpc/message.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index 86424653f..dd9d198ed 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 0966fb6d2..74f0879af 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -247,12 +247,6 @@ namespace cryptonote auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins); if (!access_control_origins_input.empty()) { - if (!config.login) - { - LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RPC server password --") << arg.rpc_login.name << tr(" cannot be empty")); - return boost::none; - } - std::vector<std::string> access_control_origins; boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(",")); std::for_each(access_control_origins.begin(), access_control_origins.end(), std::bind(&boost::trim<std::string>, std::placeholders::_1, std::locale::classic())); diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index f06e539bd..0b0dac57a 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h index 9757fc462..c84460549 100644 --- a/src/rpc/rpc_handler.h +++ b/src/rpc/rpc_handler.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index f0c513911..2e05f04fb 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment.h b/src/rpc/rpc_payment.h index fdf1f953f..e93f27d24 100644 --- a/src/rpc/rpc_payment.h +++ b/src/rpc/rpc_payment.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_costs.h b/src/rpc/rpc_payment_costs.h index 9bc0f8a2b..6ba4e8064 100644 --- a/src/rpc/rpc_payment_costs.h +++ b/src/rpc/rpc_payment_costs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_signature.cpp b/src/rpc/rpc_payment_signature.cpp index a5dc21717..4e4798aae 100644 --- a/src/rpc/rpc_payment_signature.cpp +++ b/src/rpc/rpc_payment_signature.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_signature.h b/src/rpc/rpc_payment_signature.h index bf7ee061f..55ba49e9a 100644 --- a/src/rpc/rpc_payment_signature.h +++ b/src/rpc/rpc_payment_signature.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_version_str.cpp b/src/rpc/rpc_version_str.cpp index b8b231c16..528c92415 100644 --- a/src/rpc/rpc_version_str.cpp +++ b/src/rpc/rpc_version_str.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_version_str.h b/src/rpc/rpc_version_str.h index b0cb47c5f..b81a76980 100644 --- a/src/rpc/rpc_version_str.h +++ b/src/rpc/rpc_version_str.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/zmq_pub.cpp b/src/rpc/zmq_pub.cpp index 074b55207..81e6de99a 100644 --- a/src/rpc/zmq_pub.cpp +++ b/src/rpc/zmq_pub.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/rpc/zmq_pub.h b/src/rpc/zmq_pub.h index c636e1d7b..9554f26be 100644 --- a/src/rpc/zmq_pub.h +++ b/src/rpc/zmq_pub.h @@ -1,4 +1,5 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2020-2022, The Monero Project + // // All rights reserved. // diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index 4028df96a..398a0499a 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/rpc/zmq_server.h b/src/rpc/zmq_server.h index ddf44b411..2d4049baa 100644 --- a/src/rpc/zmq_server.h +++ b/src/rpc/zmq_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt index 34e274b6c..d3b9e57ca 100644 --- a/src/serialization/CMakeLists.txt +++ b/src/serialization/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2020, The Monero Project +# Copyright (c) 2016-2022, The Monero Project # # All rights reserved. # @@ -31,8 +31,7 @@ set(serialization_sources set(serialization_headers) -set(serialization_private_headers - json_object.h) +monero_find_all_headers(serialization_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(serialization ${serialization_private_headers}) diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index acda70039..07a4ec169 100644 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/binary_utils.h b/src/serialization/binary_utils.h index 8444f220e..9b41e9529 100644 --- a/src/serialization/binary_utils.h +++ b/src/serialization/binary_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/container.h b/src/serialization/container.h index 77681ec93..7b59e9408 100644 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/containers.h b/src/serialization/containers.h index bc4a89527..dd2de829a 100644 --- a/src/serialization/containers.h +++ b/src/serialization/containers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index d635a9ee9..c2a4846ee 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -85,6 +85,7 @@ BLOB_SERIALIZER(crypto::secret_key); BLOB_SERIALIZER(crypto::key_derivation); BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); +BLOB_SERIALIZER(crypto::view_tag); VARIANT_TAG(debug_archive, crypto::hash, "hash"); VARIANT_TAG(debug_archive, crypto::hash8, "hash8"); VARIANT_TAG(debug_archive, crypto::public_key, "public_key"); @@ -92,4 +93,5 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); VARIANT_TAG(debug_archive, crypto::signature, "signature"); +VARIANT_TAG(debug_archive, crypto::view_tag, "view_tag"); diff --git a/src/serialization/debug_archive.h b/src/serialization/debug_archive.h index e8ccb9a58..479ed0cac 100644 --- a/src/serialization/debug_archive.h +++ b/src/serialization/debug_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h index 75d1fd13f..b13693c26 100644 --- a/src/serialization/difficulty_type.h +++ b/src/serialization/difficulty_type.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020, The Monero Project +// Copyright (c) 2019-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index bab6dfa94..8c4486d05 100644 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index b03da1edc..8de5860f6 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // @@ -300,7 +300,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) } const auto& rsig = tx.rct_signatures; - if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) + if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.bulletproofs_plus.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) tx.pruned = true; } @@ -563,6 +563,27 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) GET_FROM_JSON_OBJECT(val, txout.key, key); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_tagged_key& txout) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, key, txout.key); + INSERT_INTO_JSON_OBJECT(dest, view_tag, txout.view_tag); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_tagged_key& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.key, key); + GET_FROM_JSON_OBJECT(val, txout.view_tag, view_tag); +} + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_out& txout) { dest.StartObject(); @@ -578,6 +599,10 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::t { INSERT_INTO_JSON_OBJECT(dest, to_key, output); } + void operator()(cryptonote::txout_to_tagged_key const& output) const + { + INSERT_INTO_JSON_OBJECT(dest, to_tagged_key, output); + } void operator()(cryptonote::txout_to_script const& output) const { INSERT_INTO_JSON_OBJECT(dest, to_script, output); @@ -616,6 +641,12 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) fromJsonValue(elem.value, tmpVal); txout.target = std::move(tmpVal); } + else if (elem.name == "to_tagged_key") + { + cryptonote::txout_to_tagged_key tmpVal; + fromJsonValue(elem.value, tmpVal); + txout.target = std::move(tmpVal); + } else if (elem.name == "to_script") { cryptonote::txout_to_script tmpVal; @@ -1100,13 +1131,14 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::rctSig& } // prunable - if (!sig.p.bulletproofs.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) + if (!sig.p.bulletproofs.empty() || !sig.p.bulletproofs_plus.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) { dest.Key("prunable"); dest.StartObject(); INSERT_INTO_JSON_OBJECT(dest, range_proofs, sig.p.rangeSigs); INSERT_INTO_JSON_OBJECT(dest, bulletproofs, sig.p.bulletproofs); + INSERT_INTO_JSON_OBJECT(dest, bulletproofs_plus, sig.p.bulletproofs_plus); INSERT_INTO_JSON_OBJECT(dest, mlsags, sig.p.MGs); INSERT_INTO_JSON_OBJECT(dest, pseudo_outs, sig.get_pseudo_outs()); @@ -1141,6 +1173,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) GET_FROM_JSON_OBJECT(prunable->value, sig.p.rangeSigs, range_proofs); GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs, bulletproofs); + GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs_plus, bulletproofs_plus); GET_FROM_JSON_OBJECT(prunable->value, sig.p.MGs, mlsags); GET_FROM_JSON_OBJECT(prunable->value, pseudo_outs, pseudo_outs); @@ -1150,6 +1183,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) { sig.p.rangeSigs.clear(); sig.p.bulletproofs.clear(); + sig.p.bulletproofs_plus.clear(); sig.p.MGs.clear(); sig.get_pseudo_outs().clear(); } @@ -1258,6 +1292,41 @@ void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p) GET_FROM_JSON_OBJECT(val, p.t, t); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, V, p.V); + INSERT_INTO_JSON_OBJECT(dest, A, p.A); + INSERT_INTO_JSON_OBJECT(dest, A1, p.A1); + INSERT_INTO_JSON_OBJECT(dest, B, p.B); + INSERT_INTO_JSON_OBJECT(dest, r1, p.r1); + INSERT_INTO_JSON_OBJECT(dest, s1, p.s1); + INSERT_INTO_JSON_OBJECT(dest, d1, p.d1); + INSERT_INTO_JSON_OBJECT(dest, L, p.L); + INSERT_INTO_JSON_OBJECT(dest, R, p.R); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, p.V, V); + GET_FROM_JSON_OBJECT(val, p.A, A); + GET_FROM_JSON_OBJECT(val, p.A1, A1); + GET_FROM_JSON_OBJECT(val, p.B, B); + GET_FROM_JSON_OBJECT(val, p.r1, r1); + GET_FROM_JSON_OBJECT(val, p.s1, s1); + GET_FROM_JSON_OBJECT(val, p.d1, d1); + GET_FROM_JSON_OBJECT(val, p.L, L); + GET_FROM_JSON_OBJECT(val, p.R, R); +} + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig) { dest.StartObject(); diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index c858faf5a..968707165 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020, The Monero Project +// Copyright (c) 2016-2022, The Monero Project // // All rights reserved. // @@ -230,6 +230,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_key& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_tagged_key& txout); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_tagged_key& txout); + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_out& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); @@ -292,6 +295,9 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::Bulletproof& p); void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p); +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p); + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); diff --git a/src/serialization/json_utils.h b/src/serialization/json_utils.h index 8115877dc..63f4bc043 100644 --- a/src/serialization/json_utils.h +++ b/src/serialization/json_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/pair.h b/src/serialization/pair.h index 2d9a89242..f18260dc8 100644 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 4acadeab3..381d29cfc 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -132,13 +132,6 @@ inline bool do_serialize(Archive &ar, bool &v) return true; } -// Never used in the code base -// #ifndef __GNUC__ -// #ifndef constexpr -// #define constexpr -// #endif -// #endif - /* the following add a trait to a set and define the serialization DSL*/ /*! \macro BLOB_SERIALIZER diff --git a/src/serialization/string.h b/src/serialization/string.h index f1f8f4ab0..976924602 100644 --- a/src/serialization/string.h +++ b/src/serialization/string.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/serialization/variant.h b/src/serialization/variant.h index 6debb63d1..2b3c75ce5 100644 --- a/src/serialization/variant.h +++ b/src/serialization/variant.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index a0820c8eb..b659d9f48 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # @@ -31,8 +31,7 @@ set(simplewallet_sources set(simplewallet_headers) -set(simplewallet_private_headers - simplewallet.h) +monero_find_all_headers(simplewallet_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(simplewallet ${simplewallet_private_headers}) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index dc031b36c..a8f4e5a07 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -99,10 +99,6 @@ typedef cryptonote::simple_wallet sw; #define EXTENDED_LOGS_FILE "wallet_details.log" -#define DEFAULT_MIX 10 - -#define MIN_RING_SIZE 11 // Used to inform user about min ring size -- does not track actual protocol - #define OLD_AGE_WARN_THRESHOLD (30 * 86400 / DIFFICULTY_TARGET_V2) // 30 days #define LOCK_IDLE_SCOPE() \ @@ -146,6 +142,19 @@ typedef cryptonote::simple_wallet sw; #define MIN_PAYMENT_RATE 0.01f // per hash #define MAX_MNEW_ADDRESSES 1000 +#define CHECK_MULTISIG_ENABLED() \ + do \ + { \ + if (!m_wallet->is_multisig_enabled()) \ + { \ + fail_msg_writer() << tr("Multisig is disabled."); \ + fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member."); \ + fail_msg_writer() << tr("You can enable it with:"); \ + fail_msg_writer() << tr(" set enable-multisig-experimental 1"); \ + return false; \ + } \ + } while(0) + enum TransferType { Transfer, TransferLocked, @@ -235,7 +244,6 @@ namespace const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); - const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]"); const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); @@ -991,12 +999,14 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std: bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); prepare_multisig_main(args, false); return true; } bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -1021,7 +1031,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); - std::string multisig_info = m_wallet->get_multisig_info(); + std::string multisig_info = m_wallet->get_multisig_first_kex_msg(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); @@ -1036,12 +1046,14 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool simple_wallet::make_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); make_multisig_main(args, false); return true; } bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -1092,7 +1104,9 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo auto local_args = args; local_args.erase(local_args.begin()); std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); - if (!multisig_extra_info.empty()) + bool ready; + m_wallet->multisig(&ready); + if (!ready) { success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; @@ -1122,65 +1136,15 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo return true; } -bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) -{ - bool ready; - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } - - const auto pwd_container = get_and_verify_password(); - if(pwd_container == boost::none) - { - fail_msg_writer() << tr("Your original password was incorrect."); - return true; - } - - if (!m_wallet->multisig(&ready)) - { - fail_msg_writer() << tr("This wallet is not multisig"); - return true; - } - if (ready) - { - fail_msg_writer() << tr("This wallet is already finalized"); - return true; - } - - LOCK_IDLE_SCOPE(); - - if (args.size() < 2) - { - PRINT_USAGE(USAGE_FINALIZE_MULTISIG); - return true; - } - - try - { - if (!m_wallet->finalize_multisig(pwd_container->password(), args)) - { - fail_msg_writer() << tr("Failed to finalize multisig"); - return true; - } - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); - return true; - } - - return true; -} - bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); exchange_multisig_keys_main(args, false); return true; } bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1205,7 +1169,7 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & return false; } - if (args.size() < 2) + if (args.size() < 1) { PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); return false; @@ -1214,7 +1178,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & try { std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args); - if (!multisig_extra_info.empty()) + bool ready; + m_wallet->multisig(&ready); + if (!ready) { message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; @@ -1242,12 +1208,14 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & bool simple_wallet::export_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); export_multisig_main(args, false); return true; } bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1307,12 +1275,14 @@ bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, b bool simple_wallet::import_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); import_multisig_main(args, false); return true; } bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) @@ -1402,12 +1372,14 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); sign_multisig_main(args, false); return true; } bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1517,12 +1489,14 @@ bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, boo bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); submit_multisig_main(args, false); return true; } bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -1604,6 +1578,7 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -2525,59 +2500,6 @@ bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = s return true; } -bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - if (m_wallet->watch_only()) - { - fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); - return true; - } - try - { - if (strchr(args[1].c_str(), '-')) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - uint32_t ring_size = boost::lexical_cast<uint32_t>(args[1]); - if (ring_size < MIN_RING_SIZE && ring_size != 0) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - - if (ring_size != 0 && ring_size != DEFAULT_MIX+1) - { - if (m_wallet->use_fork_rules(8, 0)) - { - message_writer() << tr("WARNING: from v8, ring size will be fixed and this setting will be ignored."); - } - else - { - message_writer() << tr("WARNING: this is a non default ring size, which may harm your privacy. Default is recommended."); - } - } - - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - m_wallet->default_mixin(ring_size > 0 ? ring_size - 1 : 0); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - } - return true; - } - catch(const boost::bad_lexical_cast &) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - catch(...) - { - fail_msg_writer() << tr("could not change default ring size"); - return true; - } -} - bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { uint32_t priority = 0; @@ -3041,6 +2963,19 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->show_wallet_name_when_locked(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::set_inactivity_lock_timeout(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { #ifdef _WIN32 @@ -3167,6 +3102,25 @@ bool simple_wallet::set_load_deprecated_formats(const std::vector<std::string> & return true; } +bool simple_wallet::set_enable_multisig(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + if (args.size() < 2) + { + fail_msg_writer() << tr("Value not specified"); + return true; + } + + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->enable_multisig(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if(args.empty()) @@ -3426,8 +3380,6 @@ simple_wallet::simple_wallet() " Whether to print detailed information about ring members during confirmation.\n " "store-tx-info <1|0>\n " " Whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference.\n " - "default-ring-size <n>\n " - " Set the default ring size (obsolete).\n " "auto-refresh <1|0>\n " " Whether to automatically synchronize new blocks from the daemon.\n " "refresh-type <full|optimize-coinbase|no-coinbase|default>\n " @@ -3484,6 +3436,10 @@ simple_wallet::simple_wallet() " Whether to automatically start mining for RPC payment if the daemon requires it.\n" "credits-target <unsigned int>\n" " The RPC payment credits balance to target (0 for default).\n " + "show-wallet-name-when-locked <1|0>\n " + " Set this if you would like to display the wallet name when locked.\n " + "enable-multisig-experimental <1|0>\n " + " Set this to allow multisig commands. Multisig may currently be exploitable if parties do not trust each other.\n " "inactivity-lock-timeout <unsigned int>\n " " How many seconds to wait before locking the wallet (0 to disable).")); m_cmd_binder.set_handler("encrypted_seed", @@ -3544,7 +3500,7 @@ simple_wallet::simple_wallet() "** Set of address indices used as inputs in this transfer.")); m_cmd_binder.set_handler("export_transfers", boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_transfers, _1), - tr("export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>]"), + tr("export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>] [option=<with_keys>]"), tr("Export to CSV the incoming/outgoing transfers within an optional height range.")); m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::on_command, this, &simple_wallet::unspent_outputs, _1), @@ -3627,10 +3583,6 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1), tr(USAGE_MAKE_MULTISIG), tr("Turn this wallet into a multisig wallet")); - m_cmd_binder.set_handler("finalize_multisig", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1), - tr(USAGE_FINALIZE_MULTISIG), - tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("exchange_multisig_keys", boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1), tr(USAGE_EXCHANGE_MULTISIG_KEYS), @@ -3893,6 +3845,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; success_msg_writer() << "device-name = " << m_wallet->device_name(); success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); + success_msg_writer() << "show-wallet-name-when-locked = " << m_wallet->show_wallet_name_when_locked(); success_msg_writer() << "inactivity-lock-timeout = " << m_wallet->inactivity_lock_timeout() #ifdef _WIN32 << " (disabled on Windows)" @@ -3902,6 +3855,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); success_msg_writer() << "credits-target = " << m_wallet->credits_target(); success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats(); + success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled(); return true; } else @@ -3937,7 +3891,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4, or one of ") << join_priority_strings(", ")); @@ -3960,6 +3913,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); @@ -3968,6 +3922,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); + CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -5718,14 +5673,18 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { if (m_locked) return; + std::stringstream burn; + if (burnt != 0) { + burn << " (" << print_money(amount) << " yet " << print_money(burnt) << " was burnt)"; + } message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << ", " << + print_money(amount - burnt) << burn.str() << ", " << tr("idx ") << subaddr_index; const uint64_t warn_height = m_wallet->nettype() == TESTNET ? 1000000 : m_wallet->nettype() == STAGENET ? 50000 : 1650000; @@ -6061,6 +6020,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args auto local_args = args; LOCK_IDLE_SCOPE(); + std::set<uint32_t> subaddr_indices; bool filter = false; bool available = false; bool verbose = false; @@ -6086,6 +6046,11 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args verbose = true; else if (local_args[0] == "uses") uses = true; + else if (local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + } else { fail_msg_writer() << tr("Invalid keyword: ") << local_args.front(); @@ -6098,14 +6063,6 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args PAUSE_READLINE(); - std::set<uint32_t> subaddr_indices; - if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") - { - if (!parse_subaddress_indices(local_args[0], subaddr_indices)) - return true; - local_args.erase(local_args.begin()); - } - if (local_args.size() > 0) { PRINT_USAGE(USAGE_INCOMING_TRANSFERS); @@ -6522,6 +6479,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) { const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); + + const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); + if (show_wallet_name) + { + tools::msg_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + tools::msg_writer() << tr("Network type: ") << ( + m_wallet->nettype() == cryptonote::TESTNET ? tr("Testnet") : + m_wallet->nettype() == cryptonote::STAGENET ? tr("Stagenet") : tr("Mainnet") + ); + } try { if (get_and_verify_password()) @@ -6571,7 +6538,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + const size_t min_ring_size = m_wallet->get_min_ring_size(); + size_t fake_outs_count = min_ring_size - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -6647,7 +6615,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri vector<cryptonote::address_parse_info> dsts_info; vector<cryptonote::tx_destination_entry> dsts; - size_t num_subaddresses = 0; for (size_t i = 0; i < local_args.size(); ) { dsts_info.emplace_back(); @@ -6706,7 +6673,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri de.addr = info.address; de.is_subaddress = info.is_subaddress; de.is_integrated = info.has_payment_id; - num_subaddresses += info.is_subaddress; if (info.has_payment_id || !payment_id_uri.empty()) { @@ -6894,7 +6860,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (vin.type() == typeid(txin_to_key)) { const txin_to_key& in_to_key = boost::get<txin_to_key>(vin); - if (in_to_key.key_offsets.size() != DEFAULT_MIX + 1) + if (in_to_key.key_offsets.size() != min_ring_size) default_ring_size = false; } } @@ -6998,18 +6964,33 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_TRANSFER); + return true; + } transfer_main(Transfer, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_TRANSFER); + return true; + } transfer_main(TransferLocked, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL); + return true; + } sweep_main(m_current_subaddress_account, 0, true, args_); return true; } @@ -7067,6 +7048,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7172,7 +7154,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + size_t fake_outs_count = m_wallet->get_min_ring_size() - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -7371,6 +7353,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7449,7 +7432,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + size_t fake_outs_count = m_wallet->get_min_ring_size() - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -7605,6 +7588,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7705,6 +7689,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) if (args_.size() < 1) { fail_msg_writer() << tr("missing threshold amount"); + PRINT_USAGE(USAGE_SWEEP_BELOW); return true; } if (!cryptonote::parse_amount(below, args_[0])) @@ -8053,6 +8038,15 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::secret_key> additional_tx_keys) +{ + ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + return oss.str(); +} + bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; @@ -8082,11 +8076,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) bool found_tx_key = m_wallet->get_tx_key(txid, tx_key, additional_tx_keys); if (found_tx_key) { - ostringstream oss; - oss << epee::string_tools::pod_to_hex(tx_key); - for (size_t i = 0; i < additional_tx_keys.size(); ++i) - oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); - success_msg_writer() << tr("Tx key: ") << oss.str(); + std::string stream = get_tx_key_stream(tx_key, additional_tx_keys); + success_msg_writer() << tr("Tx key: ") << stream; return true; } else @@ -8897,13 +8888,11 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) { std::vector<std::string> local_args = args_; - if(local_args.size() > 5) { - fail_msg_writer() << tr("usage: export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>]"); + if(local_args.size() > 6) { + fail_msg_writer() << tr("usage: export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>] [option=<with_keys>]"); return true; } - LOCK_IDLE_SCOPE(); - std::vector<transfer_view> all_transfers; // might consumes arguments in local_args @@ -8917,17 +8906,36 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) filename = local_args[0].substr(7, -1); local_args.erase(local_args.begin()); } + // check for export with tx keys + bool export_keys = false; + if (local_args.size() > 0 && local_args[0].substr(0, 7) == "option=") + { + export_keys = local_args[0].substr(7, -1) == "with_keys"; + local_args.erase(local_args.begin()); + } + if (export_keys) + { + if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR) + { + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } + SCOPED_WALLET_UNLOCK(); + } else + { + LOCK_IDLE_SCOPE(); + } std::ofstream file(filename); // header file << - boost::format("%8.8s,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,%s,%s") % - tr("block") % tr("direction") % tr("unlocked") % tr("timestamp") % tr("amount") % tr("running balance") % tr("hash") % tr("payment ID") % tr("fee") % tr("destination") % tr("amount") % tr("index") % tr("note") + boost::format("%8.8s,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,%s,%s,%s") % + tr("block") % tr("direction") % tr("unlocked") % tr("timestamp") % tr("amount") % tr("running balance") % tr("hash") % tr("payment ID") % tr("fee") % tr("destination") % tr("amount") % tr("index") % tr("note") % tr("tx key") << std::endl; uint64_t running_balance = 0; - auto formatter = boost::format("%8.8llu,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,\"%s\",%s"); + auto formatter = boost::format("%8.8llu,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,\"%s\",%s,%s"); for (const auto& transfer : all_transfers) { @@ -8940,6 +8948,15 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) running_balance -= transfer.amount + transfer.fee; } + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + bool found_tx_key = m_wallet->get_tx_key(transfer.hash, tx_key, additional_tx_keys); + std::string key_string; + if (export_keys && found_tx_key) + { + key_string = get_tx_key_stream(tx_key, additional_tx_keys); + } + file << formatter % transfer.block % transfer.direction @@ -8954,6 +8971,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) % (transfer.outputs.size() ? print_money(transfer.outputs[0].second) : "") % boost::algorithm::join(transfer.index | boost::adaptors::transformed([](uint32_t i) { return std::to_string(i); }), ", ") % transfer.note + % key_string << std::endl; for (size_t i = 1; i < transfer.outputs.size(); ++i) @@ -8972,6 +8990,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) % print_money(transfer.outputs[i].second) % "" % "" + % "" << std::endl; } } @@ -9593,8 +9612,8 @@ void simple_wallet::print_accounts(const std::string& tag) total_balance += m_wallet->balance(account_index, false); total_unlocked_balance += m_wallet->unlocked_balance(account_index, false); } - success_msg_writer() << tr("----------------------------------------------------------------------------------"); - success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); + success_msg_writer() << tr("------------------------------------------------------------------------------------"); + success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) @@ -9818,7 +9837,7 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg { if (info.has_payment_id) { - success_msg_writer() << boost::format(tr("Integrated address: %s, payment ID: %s")) % + success_msg_writer() << boost::format(tr("Standard address: %s, payment ID: %s")) % get_account_address_as_str(m_wallet->nettype(), false, info.address) % epee::string_tools::pod_to_hex(info.payment_id); device_show_integrated(info.payment_id); } @@ -10973,8 +10992,8 @@ void simple_wallet::mms_init(const std::vector<std::string> &args) std::vector<std::string> numbers; boost::split(numbers, mn, boost::is_any_of("/")); bool mn_ok = (numbers.size() == 2) - && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100) - && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers); + && get_number_from_arg(numbers[1], num_authorized_signers, 2, config::MULTISIG_MAX_SIGNERS) + && get_number_from_arg(numbers[0], num_required_signers, 1, num_authorized_signers); if (!mn_ok) { fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers"); @@ -11602,6 +11621,7 @@ void simple_wallet::mms_auto_config(const std::vector<std::string> &args) bool simple_wallet::mms(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); try { m_wallet->get_multisig_wallet_state(); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 8780bee1d..6a9fa149d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -125,7 +125,6 @@ namespace cryptonote bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>()); bool set_print_ring_members(const std::vector<std::string> &args = std::vector<std::string>()); bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>()); - bool set_default_ring_size(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>()); bool set_refresh_type(const std::vector<std::string> &args = std::vector<std::string>()); bool set_confirm_missing_payment_id(const std::vector<std::string> &args = std::vector<std::string>()); @@ -148,11 +147,13 @@ namespace cryptonote bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>()); bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>()); bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); @@ -233,7 +234,6 @@ namespace cryptonote bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool make_multisig(const std::vector<std::string>& args); bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); - bool finalize_multisig(const std::vector<std::string> &args); bool exchange_multisig_keys(const std::vector<std::string> &args); bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); @@ -346,7 +346,7 @@ namespace cryptonote //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 2dd64a38f..6095f99d5 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 655cdfefd..af7948d8a 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2022, The Monero Project # # All rights reserved. # diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index 0b0e17464..c73653e37 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h index 40da46853..5b0655000 100644 --- a/src/wallet/api/address_book.h +++ b/src/wallet/api/address_book.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index f7e74591f..70a702796 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 274c60851..0a9779c07 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.cpp b/src/wallet/api/subaddress.cpp index 227bb343d..9e358b4c8 100644 --- a/src/wallet/api/subaddress.cpp +++ b/src/wallet/api/subaddress.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h index 18c9ed59e..53ece126d 100644 --- a/src/wallet/api/subaddress.h +++ b/src/wallet/api/subaddress.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.cpp b/src/wallet/api/subaddress_account.cpp index 5e502ed5b..e8153df3d 100644 --- a/src/wallet/api/subaddress_account.cpp +++ b/src/wallet/api/subaddress_account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h index 1318d4da5..94cab47fb 100644 --- a/src/wallet/api/subaddress_account.h +++ b/src/wallet/api/subaddress_account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 4649089ea..9f5e41156 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index fe77253e6..1d52f4a69 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index edbdc469a..572b04316 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index 5eeeb04c2..6337f2aaa 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 4ccfafebd..6165a2240 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 07649e39e..30065a7fa 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index 19151b5f6..d8dcedc5f 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 0afbda705..1ee2e20b6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -154,18 +154,20 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount) + << ", amount: " << print_money(amount - burnt) + << ", burnt: " << print_money(burnt) + << ", raw_output_value: " << print_money(amount) << ", idx: " << subaddr_index); // do not signal on received tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { - m_listener->moneyReceived(tx_hash, amount); + m_listener->moneyReceived(tx_hash, amount - burnt); m_listener->updated(); } } @@ -450,7 +452,7 @@ WalletImpl::~WalletImpl() LOG_PRINT_L1(__FUNCTION__); m_wallet->callback(NULL); // Pause refresh thread - prevents refresh from starting again - pauseRefresh(); + WalletImpl::pauseRefresh(); // Call the method directly (not polymorphically) to protect against UB in destructor. // Close wallet - stores cache and stops ongoing refresh operation close(false); // do not store wallet as part of the closing activities // Stop refresh thread @@ -1280,6 +1282,42 @@ bool WalletImpl::importOutputs(const string &filename) return true; } +bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) +{ + if (txids.empty()) + { + setStatusError(string(tr("Failed to scan transactions: no transaction ids provided."))); + return false; + } + + // Parse and dedup args + std::unordered_set<crypto::hash> txids_u; + for (const auto &s : txids) + { + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(s, txid)) + { + setStatusError(string(tr("Invalid txid specified: ")) + s); + return false; + } + txids_u.insert(txid); + } + std::vector<crypto::hash> txids_v(txids_u.begin(), txids_u.end()); + + try + { + m_wallet->scan_tx(txids_v); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to scan transaction: " << e.what()); + setStatusError(string(tr("Failed to scan transaction: ")) + e.what()); + return false; + } + + return true; +} + void WalletImpl::addSubaddressAccount(const std::string& label) { m_wallet->add_subaddress_account(label); @@ -1332,7 +1370,7 @@ MultisigState WalletImpl::multisig() const { string WalletImpl::getMultisigInfo() const { try { clearStatus(); - return m_wallet->get_multisig_info(); + return m_wallet->get_multisig_first_kex_msg(); } catch (const exception& e) { LOG_ERROR("Error on generating multisig info: " << e.what()); setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); @@ -1341,7 +1379,7 @@ string WalletImpl::getMultisigInfo() const { return string(); } -string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { +string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) { try { clearStatus(); @@ -1366,30 +1404,12 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info); } catch (const exception& e) { LOG_ERROR("Error on exchanging multisig keys: " << e.what()); - setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what()); } return string(); } -bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { - try { - clearStatus(); - checkMultisigWalletNotReady(m_wallet); - - if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { - return true; - } - - setStatusError(tr("Failed to finalize multisig wallet creation")); - } catch (const exception& e) { - LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what()); - setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); - } - - return false; -} - bool WalletImpl::exportMultisigImages(string& images) { try { clearStatus(); @@ -1760,8 +1780,9 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str extra_size, m_wallet->use_fork_rules(8, 0), m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), + m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), + m_wallet->use_fork_rules(HF_VERSION_VIEW_TAGS, 0), m_wallet->get_base_fee(), - m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), m_wallet->get_fee_quantization_mask()); } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 67fc2c08a..018b2a0ed 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -147,7 +147,6 @@ public: std::string getMultisigInfo() const override; std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; std::string exchangeMultisigKeys(const std::vector<std::string> &info) override; - bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; @@ -170,6 +169,7 @@ public: bool importKeyImages(const std::string &filename) override; bool exportOutputs(const std::string &filename, bool all = false) override; bool importOutputs(const std::string &filename) override; + bool scanTransactions(const std::vector<std::string> &txids) override; virtual void disposeTransaction(PendingTransaction * t) override; virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index f9c421a93..b67bce60c 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -423,7 +423,6 @@ struct WalletListener /** * @brief Interface for wallet operations. - * TODO: check if /include/IWallet.h is still actual */ struct Wallet { @@ -790,7 +789,7 @@ struct Wallet /** * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call - * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @param threshold - number of required signers to make valid transaction. Must be <= number of participants * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info */ virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; @@ -801,12 +800,6 @@ struct Wallet */ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0; /** - * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation - * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call - * @return true if success - */ - virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; - /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images * @return true if success @@ -934,6 +927,13 @@ struct Wallet */ virtual bool importOutputs(const std::string &filename) = 0; + /*! + * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy + * \param txids - list of transaction ids + * \return - true on success + */ + virtual bool scanTransactions(const std::vector<std::string> &txids) = 0; + virtual TransactionHistory * history() = 0; virtual AddressBook * addressBook() = 0; virtual Subaddress * subaddress() = 0; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 417a27db5..e81b8f83a 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -49,6 +49,11 @@ namespace epee { namespace Monero { +WalletManagerImpl::WalletManagerImpl() +{ + tools::set_strict_default_file_permissions(true); +} + Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds) { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index cf3056a17..a223e1df9 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -95,7 +95,7 @@ public: bool setProxy(const std::string &address) override; private: - WalletManagerImpl() {} + WalletManagerImpl(); friend struct WalletManagerFactory; net::http::client m_http_client; std::string m_errorString; diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index 34b4f440b..cf1d91d5a 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index 0f53587d4..c5421a702 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index 4dd4b8f01..c985eb583 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h index 557833f2c..b7d3c8107 100644 --- a/src/wallet/message_transporter.h +++ b/src/wallet/message_transporter.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index a576c267c..7810abdd2 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -70,6 +70,7 @@ void NodeRPCProxy::invalidate() m_dynamic_base_fee_estimate = 0; m_dynamic_base_fee_estimate_cached_height = 0; m_dynamic_base_fee_estimate_grace_blocks = 0; + m_dynamic_base_fee_estimate_vector.clear(); m_fee_quantization_mask = 1; m_rpc_version = 0; m_target_height = 0; @@ -210,7 +211,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, return boost::optional<std::string>(); } -boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) +boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) { uint64_t height; @@ -238,13 +239,24 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_ m_dynamic_base_fee_estimate = resp_t.fee; m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; + m_dynamic_base_fee_estimate_vector = !resp_t.fees.empty() ? std::move(resp_t.fees) : std::vector<uint64_t>{m_dynamic_base_fee_estimate}; m_fee_quantization_mask = resp_t.quantization_mask; } - fee = m_dynamic_base_fee_estimate; + fees = m_dynamic_base_fee_estimate_vector; return boost::optional<std::string>(); } +boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) +{ + std::vector<uint64_t> fees; + auto res = get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees); + if (res) + return res; + fee = fees[0]; + return boost::none; +} + boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) { uint64_t height; @@ -306,7 +318,12 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, boo m_rpc_payment_seed_height = resp_t.seed_height; m_rpc_payment_cookie = resp_t.cookie; - if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43) + if (m_rpc_payment_diff == 0) + { + // If no payment required daemon doesn't give us back a hashing blob + m_rpc_payment_blob.clear(); + } + else if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43) { MERROR("Invalid hashing blob: " << resp_t.hashing_blob); return std::string("Invalid hashing blob"); diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index f5e3fca5f..07675cdb0 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2022, The Monero Project // // All rights reserved. // @@ -56,6 +56,7 @@ public: boost::optional<std::string> get_adjusted_time(uint64_t &adjusted_time); boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height); boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee); + boost::optional<std::string> get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees); boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask); boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); @@ -85,6 +86,7 @@ private: uint64_t m_dynamic_base_fee_estimate; uint64_t m_dynamic_base_fee_estimate_cached_height; uint64_t m_dynamic_base_fee_estimate_grace_blocks; + std::vector<uint64_t> m_dynamic_base_fee_estimate_vector; uint64_t m_fee_quantization_mask; uint64_t m_adjusted_time; uint32_t m_rpc_version; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 025a2037f..7e4f12f5b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // @@ -343,12 +344,15 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote return remove_rings(chacha_key, key_images); } -bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) +bool ringdb::get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs) { MDB_txn *txn; int dbr; bool tx_active = false; + all_outs.clear(); + all_outs.reserve(key_images.size()); + dbr = resize_env(env, filename.c_str(), 0); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); @@ -356,6 +360,10 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; + for (size_t i = 0; i < key_images.size(); ++i) + { + const crypto::key_image &key_image = key_images[i]; + MDB_val key, data; std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); @@ -366,6 +374,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return false; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + std::vector<uint64_t> outs; bool try_v0 = false; std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1); try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; } @@ -379,6 +388,9 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); outs = cryptonote::relative_output_offsets_to_absolute(outs); MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + all_outs.push_back(std::move(outs)); + + } dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr))); @@ -386,20 +398,33 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } -bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative) +bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) +{ + std::vector<std::vector<uint64_t>> all_outs; + if (!get_rings(chacha_key, std::vector<crypto::key_image>(1, key_image), all_outs)) + return false; + outs = std::move(all_outs.front()); + return true; +} + +bool ringdb::set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative) { MDB_txn *txn; int dbr; bool tx_active = false; - dbr = resize_env(env, filename.c_str(), outs.size() * 64); + size_t n_outs = 0; + for (const auto &e: rings) + n_outs += e.second.size(); + dbr = resize_env(env, filename.c_str(), n_outs * 64); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key); + for (const auto &e: rings) + store_relative_ring(txn, dbi_rings, e.first, relative ? e.second : cryptonote::absolute_output_offsets_to_relative(e.second), chacha_key); dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr))); @@ -407,6 +432,13 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } +bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative) +{ + std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings; + rings.push_back(std::make_pair(key_image, outs)); + return set_rings(chacha_key, rings, relative); +} + bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 9c7e624bc..bdecdba37 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -1,4 +1,5 @@ -// Copyright (c) 2018, The Monero Project +// Copyright (c) 2018-2022, The Monero Project + // // All rights reserved. // @@ -48,7 +49,9 @@ namespace tools bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images); bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); + bool get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + bool set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative); bool blackball(const std::pair<uint64_t, uint64_t> &output); bool blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5a4cafc32..ed153d681 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <algorithm> #include <numeric> #include <tuple> #include <queue> @@ -59,6 +60,9 @@ using namespace epee; #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "multisig/multisig.h" +#include "multisig/multisig_account.h" +#include "multisig/multisig_kex_msg.h" +#include "multisig/multisig_tx_builder_ringct.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -146,10 +150,9 @@ using namespace cryptonote; #define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12 #define DEFAULT_UNLOCK_TIME (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2) -#define RECENT_SPEND_WINDOW (50 * DIFFICULTY_TARGET_V2) +#define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2) static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; -static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; @@ -167,42 +170,6 @@ namespace return dir.string(); } - std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key) - { - std::string data; - crypto::public_key signer; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key"); - data += std::string((const char *)&signer, sizeof(crypto::public_key)); - - for (const auto &key: keys) - { - data += std::string((const char *)&key, sizeof(crypto::public_key)); - } - - data.resize(data.size() + sizeof(crypto::signature)); - - crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, signer, signer_secret_key, signature); - - return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data); - } - - std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys) - { - std::vector<crypto::public_key> public_keys; - public_keys.reserve(keys.size()); - - std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key { - crypto::public_key p; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key"); - return p; - }); - - return public_keys; - } - bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2) { if (s1.empty() || s2.empty()) @@ -314,7 +281,6 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, { keys_file = file_path; wallet_file = file_path; - boost::system::error_code e; if(string_tools::get_extension(keys_file) == "keys") {//provided keys file name wallet_file = string_tools::cut_off_extension(wallet_file); @@ -325,15 +291,15 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, mms_file = file_path + ".mms"; } -uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier) +uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes) { uint64_t kB = (bytes + 1023) / 1024; - return kB * fee_per_kb * fee_multiplier; + return kB * fee_per_kb; } -uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_multiplier, uint64_t fee_quantization_mask) +uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_quantization_mask) { - uint64_t fee = weight * base_fee * fee_multiplier; + uint64_t fee = weight * base_fee; fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask; return fee; } @@ -816,7 +782,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { size_t size = 0; @@ -840,12 +806,12 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra size += 1; // rangeSigs - if (bulletproof) + if (bulletproof || bulletproof_plus) { size_t log_padded_outputs = 0; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; - size += (2 * (6 + log_padded_outputs) + 4 + 5) * 32 + 3; + size += (2 * (6 + log_padded_outputs) + (bulletproof_plus ? 6 : (4 + 5))) * 32 + 3; } else size += (2*64*32+32+64*32) * n_outputs; @@ -856,6 +822,9 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra else size += n_inputs * (64 * (mixin+1) + 32); + if (use_view_tags) + size += n_outputs * sizeof(crypto::view_tag); + // mixRing - not serialized, can be reconstructed /* size += 2 * 32 * (mixin+1) * n_inputs; */ @@ -868,29 +837,29 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra // txnFee size += 4; - LOG_PRINT_L2("estimated " << (bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + LOG_PRINT_L2("estimated " << (bulletproof_plus ? "bulletproof plus" : bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); return size; } -size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); + return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); else - return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; + return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size + (use_view_tags ? (n_outputs * sizeof(crypto::view_tag)) : 0); } -uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { - size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); - if (use_rct && bulletproof && n_outputs > 2) + size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + if (use_rct && (bulletproof || bulletproof_plus) && n_outputs > 2) { - const uint64_t bp_base = 368; + const uint64_t bp_base = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) size_t log_padded_outputs = 2; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; uint64_t nlr = 2 * (6 + log_padded_outputs); - const uint64_t bp_size = 32 * (9 + nlr); + const uint64_t bp_size = 32 * ((bulletproof_plus ? 6 : 9) + nlr); const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5; MDEBUG("clawback on size " << size << ": " << bp_clawback); size += bp_clawback; @@ -903,17 +872,27 @@ uint8_t get_bulletproof_fork() return 8; } +uint8_t get_bulletproof_plus_fork() +{ + return HF_VERSION_BULLETPROOF_PLUS; +} + uint8_t get_clsag_fork() { return HF_VERSION_CLSAG; } -uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) +uint8_t get_view_tag_fork() +{ + return HF_VERSION_VIEW_TAGS; +} + +uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_quantization_mask) { if (use_per_byte_fee) - return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_multiplier, fee_quantization_mask); + return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_quantization_mask); else - return calculate_fee(base_fee, blob_size, fee_multiplier); + return calculate_fee(base_fee, blob_size); } bool get_short_payment_id(crypto::hash8 &payment_id8, const tools::wallet2::pending_tx &ptx, hw::device &hwdev) @@ -1024,13 +1003,7 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); - THROW_WALLET_EXCEPTION_IF(outputs_to_consider == 0, error::wallet_internal_error, "No rct outputs to consider"); - average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range - if (average_output_time == 0) { - // TODO: apply this to all cases; do so alongside a hard fork, where all clients will update at the same time, preventing anonymity puddle formation - average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); - } - THROW_WALLET_EXCEPTION_IF(average_output_time == 0, error::wallet_internal_error, "Average seconds per output cannot be 0."); + average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); // this assumes constant target over the whole rct range }; gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets): gamma_picker(rct_offsets, GAMMA_SHAPE, GAMMA_SCALE) {} @@ -1209,6 +1182,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ignore_outputs_above(MONEY_SUPPLY), m_ignore_outputs_below(0), m_track_uses(false), + m_show_wallet_name_when_locked(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), m_persistent_rpc_client_id(false), @@ -1235,8 +1209,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), - m_encrypt_keys_after_refresh(boost::none), - m_decrypt_keys_lockers(0), m_unattended(unattended), m_devices_registered(false), m_device_last_key_image_sync(0), @@ -1245,7 +1217,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_rpc_version(0), m_export_format(ExportFormat::Binary), m_load_deprecated_formats(false), - m_credits_target(0) + m_credits_target(0), + m_enable_multisig(false) { set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } @@ -1802,13 +1775,14 @@ void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivatio hw::device &hwdev = m_account.get_device(); boost::unique_lock<hw::device> hwdev_lock (hwdev); hwdev.set_mode(hw::device::TRANSACTION_PARSE); - if (o.target.type() != typeid(txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(o, output_public_key)) { tx_scan_info.error = true; LOG_ERROR("wrong type id in transaction out"); return; } - tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i, hwdev); + tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, output_public_key, derivation, additional_derivations, i, hwdev, get_output_view_tag(o)); if(tx_scan_info.received) { tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs @@ -1859,6 +1833,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); @@ -1888,22 +1863,24 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received"); THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero")); THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero")); - decrypt_keys(*pwd); - m_encrypt_keys_after_refresh = *pwd; + m_encrypt_keys_after_refresh.reset(new wallet_keys_unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, *pwd)); } } + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key"); + if (m_multisig) { - tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.pub = output_public_key; tx_scan_info.in_ephemeral.sec = crypto::null_skey; tx_scan_info.ki = rct::rct2ki(rct::zero()); } else { - bool r = cryptonote::generate_key_image_helper_precomp(m_account.get_keys(), boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki, m_account.get_device()); + bool r = cryptonote::generate_key_image_helper_precomp(m_account.get_keys(), output_public_key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != output_public_key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); } @@ -2030,8 +2007,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote int num_vouts_received = 0; tx_pub_key = pub_key_field.pub_key; - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter(tpool); const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; @@ -2101,10 +2076,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp_once, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::cref(is_out_data_ptr), std::ref(tx_scan_info[i]), std::ref(output_found[i])), true); + check_acc_out_precomp_once(tx.vout[i], derivation, additional_derivations, i, is_out_data_ptr, tx_scan_info[i], output_found[i]); } - THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); // then scan all outputs from 0 hw::device &hwdev = m_account.get_device(); boost::unique_lock<hw::device> hwdev_lock (hwdev); @@ -2124,32 +2097,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } } } - else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1 && !is_out_data_ptr) - { - for (size_t i = 0; i < tx.vout.size(); ++i) - { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp_once, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::cref(is_out_data_ptr), std::ref(tx_scan_info[i]), std::ref(output_found[i])), true); - } - THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); - - hw::device &hwdev = m_account.get_device(); - boost::unique_lock<hw::device> hwdev_lock (hwdev); - hwdev.set_mode(hw::device::NONE); - for (size_t i = 0; i < tx.vout.size(); ++i) - { - THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - if (tx_scan_info[i].received) - { - hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations); - scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs, pool); - if (!tx_scan_info[i].error) - { - tx_amounts_individual_outs[tx_scan_info[i].received->index].push_back(tx_scan_info[i].money_transfered); - } - } - } - } else { for (size_t i = 0; i < tx.vout.size(); ++i) @@ -2262,7 +2209,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += amount; notify = true; @@ -2296,7 +2243,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote tx_money_got_in_outs[tx_scan_info[o].received->index] -= m_transfers[kit->second].amount(); uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; - uint64_t extra_amount = amount - m_transfers[kit->second].amount(); + uint64_t burnt = m_transfers[kit->second].amount(); + uint64_t extra_amount = amount - burnt; if (!pool) { transfer_details &td = m_transfers[kit->second]; @@ -2339,7 +2287,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += extra_amount; notify = true; @@ -2830,25 +2778,34 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry for (size_t k = 0; k < n_vouts; ++k) { const auto &o = tx.vout[k]; - if (o.target.type() == typeid(cryptonote::txout_to_key)) + crypto::public_key output_public_key; + if (get_output_public_key(o, output_public_key)) { std::vector<crypto::key_derivation> additional_derivations; additional_derivations.reserve(tx_cache_data[txidx].additional.size()); for (const auto &iod: tx_cache_data[txidx].additional) additional_derivations.push_back(iod.derivation); - const auto &key = boost::get<txout_to_key>(o.target).key; for (size_t l = 0; l < tx_cache_data[txidx].primary.size(); ++l) { THROW_WALLET_EXCEPTION_IF(tx_cache_data[txidx].primary[l].received.size() != n_vouts, error::wallet_internal_error, "Unexpected received array size"); - tx_cache_data[txidx].primary[l].received[k] = is_out_to_acc_precomp(m_subaddresses, key, tx_cache_data[txidx].primary[l].derivation, additional_derivations, k, hwdev); + tx_cache_data[txidx].primary[l].received[k] = is_out_to_acc_precomp(m_subaddresses, output_public_key, tx_cache_data[txidx].primary[l].derivation, additional_derivations, k, hwdev, get_output_view_tag(o)); additional_derivations.clear(); } } } }; + struct geniod_params + { + const cryptonote::transaction &tx; + size_t n_outs; + size_t txidx; + }; + std::vector<geniod_params> geniods; + geniods.reserve(num_txes); txidx = 0; + uint8_t hf_version_view_tags = get_view_tag_fork(); for (size_t i = 0; i < blocks.size(); ++i) { if (should_skip_block(parsed_blocks[i].block, start_height + i)) @@ -2862,18 +2819,51 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); const cryptonote::transaction& tx = parsed_blocks[i].block.miner_tx; const size_t n_vouts = (m_refresh_type == RefreshType::RefreshOptimizeCoinbase && tx.version < 2) ? 1 : tx.vout.size(); - tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true); + if (parsed_blocks[i].block.major_version >= hf_version_view_tags) + geniods.push_back(geniod_params{ tx, n_vouts, txidx }); + else + tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true); } ++txidx; for (size_t j = 0; j < parsed_blocks[i].txes.size(); ++j) { THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); - tpool.submit(&waiter, [&, i, j, txidx](){ geniod(parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx); }, true); + if (parsed_blocks[i].block.major_version >= hf_version_view_tags) + geniods.push_back(geniod_params{ parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx }); + else + tpool.submit(&waiter, [&, i, j, txidx](){ geniod(parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx); }, true); ++txidx; } } THROW_WALLET_EXCEPTION_IF(txidx != tx_cache_data.size(), error::wallet_internal_error, "txidx did not reach expected value"); + + // View tags significantly speed up the geniod function that determines if an output belongs to the account. + // Because the speedup is so large, the overhead from submitting individual geniods to the thread pool eats into + // the benefit of executing in parallel. So to maximize the benefit from threads when view tags are enabled, + // the wallet starts submitting geniod function calls to the thread pool in batches of size GENIOD_BATCH_SIZE. + if (geniods.size()) + { + size_t GENIOD_BATCH_SIZE = 100; + size_t num_batch_txes = 0; + size_t batch_start = 0; + while (batch_start < geniods.size()) + { + size_t batch_end = std::min(batch_start + GENIOD_BATCH_SIZE, geniods.size()); + THROW_WALLET_EXCEPTION_IF(batch_end < batch_start, error::wallet_internal_error, "Thread batch end overflow"); + tpool.submit(&waiter, [&geniods, &geniod, batch_start, batch_end]() { + for (size_t i = batch_start; i < batch_end; ++i) + { + const geniod_params &gp = geniods[i]; + geniod(gp.tx, gp.n_outs, gp.txidx); + } + }, true); + num_batch_txes += batch_end - batch_start; + batch_start = batch_end; + } + THROW_WALLET_EXCEPTION_IF(num_batch_txes != geniods.size(), error::wallet_internal_error, "txes batched for thread pool did not reach expected value"); + } THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); + hwdev.set_mode(hw::device::NONE); size_t tx_cache_data_offset = 0; @@ -2895,6 +2885,11 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry " (height " + std::to_string(start_height) + "), local block id at this height: " + string_tools::pod_to_hex(m_blockchain[current_index])); + const uint64_t reorg_depth = m_blockchain.size() - current_index; + THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error, + tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") + + std::to_string(reorg_depth)); + detach_blockchain(current_index, output_tracker_cache); process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache); } @@ -3021,11 +3016,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, MTRACE("update_pool_state start"); auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { - if (m_encrypt_keys_after_refresh) - { - encrypt_keys(*m_encrypt_keys_after_refresh); - m_encrypt_keys_after_refresh = boost::none; - } + m_encrypt_keys_after_refresh.reset(); }); // get the pool state @@ -3170,14 +3161,18 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } } - // get those txes - if (!txids.empty()) + // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp + for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &p: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); - MDEBUG("asking for " << txids.size() << " transactions"); + + const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); + for (size_t n = offset; n < (offset + n_txids); ++n) { + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first)); + } + MDEBUG("asking for " << req.txs_hashes.size() << " transactions"); req.decode_as_json = false; req.prune = true; @@ -3194,7 +3189,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, MDEBUG("Got " << r << " and " << res.status); if (r && res.status == CORE_RPC_STATUS_OK) { - if (res.txs.size() == txids.size()) + if (res.txs.size() == req.txs_hashes.size()) { for (const auto &tx_entry: res.txs) { @@ -3230,7 +3225,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } else { - LOG_PRINT_L0("Expected " << txids.size() << " tx(es), got " << res.txs.size()); + LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size()); } } else @@ -3456,11 +3451,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo start_height = 0; auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { - if (m_encrypt_keys_after_refresh) - { - encrypt_keys(*m_encrypt_keys_after_refresh); - m_encrypt_keys_after_refresh = boost::none; - } + m_encrypt_keys_after_refresh.reset(); }); auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);}); @@ -3553,15 +3544,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo first = false; - if (!next_blocks.empty()) - { - const uint64_t expected_start_height = std::max(static_cast<uint64_t>(m_blockchain.size()), uint64_t(1)) - 1; - const uint64_t reorg_depth = expected_start_height - std::min(expected_start_height, next_blocks_start_height); - THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error, - tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") + - std::to_string(reorg_depth)); - } - // if we've got at least 10 blocks to refresh, assume we're starting // a long refresh, and setup a tracking output cache if we need to if (m_track_uses && (!output_tracker_cache || output_tracker_cache->empty()) && next_blocks.size() >= 10) @@ -3646,32 +3628,7 @@ bool wallet2::refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& rece //---------------------------------------------------------------------------------------------------- bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) { - uint32_t rpc_version; - boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); - // no error - if (!!result) - { - // empty string -> not connection - THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); - THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); - if (*result != CORE_RPC_STATUS_OK) - { - MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution"); - return false; - } - } - else - { - if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19)) - { - MDEBUG("Daemon is recent enough, requesting rct distribution"); - } - else - { - MDEBUG("Daemon is too old, not requesting rct distribution"); - return false; - } - } + MDEBUG("Requesting rct distribution"); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res); @@ -4018,6 +3975,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); + json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); + value2.SetInt(m_inactivity_lock_timeout); json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator()); @@ -4069,6 +4029,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetUint64(m_credits_target); json.AddMember("credits_target", value2, json.GetAllocator()); + value2.SetInt(m_enable_multisig ? 1 : 0); + json.AddMember("enable_multisig", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -4202,6 +4165,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_above = MONEY_SUPPLY; m_ignore_outputs_below = 0; m_track_uses = false; + m_show_wallet_name_when_locked = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; m_setup_background_mining = BackgroundMiningMaybe; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; @@ -4216,6 +4180,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_persistent_rpc_client_id = false; m_auto_mine_for_rpc_payment_threshold = -1.0f; m_credits_target = 0; + m_enable_multisig = false; } else if(json.IsObject()) { @@ -4376,6 +4341,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_below = field_ignore_outputs_below; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, show_wallet_name_when_locked, int, Int, false, false); + m_show_wallet_name_when_locked = field_show_wallet_name_when_locked; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT); m_inactivity_lock_timeout = field_inactivity_lock_timeout; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); @@ -4446,6 +4413,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0); m_credits_target = field_credits_target; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); + m_enable_multisig = field_enable_multisig; } else { @@ -4467,7 +4436,26 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_account.set_device(hwdev); account_public_address device_account_public_address; - THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + bool fetch_device_address = true; + + ::hw::device_cold* dev_cold = nullptr; + if (m_key_device_type == hw::device::device_type::TREZOR && (dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev)) != nullptr) { + THROW_WALLET_EXCEPTION_IF(!dev_cold->get_public_address_with_no_passphrase(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + if (device_account_public_address == m_account.get_keys().m_account_address) { + LOG_PRINT_L0("Wallet opened with an empty passphrase"); + fetch_device_address = false; + dev_cold->set_use_empty_passphrase(true); + } else { + fetch_device_address = true; + LOG_PRINT_L0("Wallet opening with an empty passphrase failed. Retry again: " << fetch_device_address); + dev_cold->reset_session(); + } + } + + if (fetch_device_address) { + THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + } + THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. If the device uses the passphrase feature, please check whether the passphrase was entered correctly (it may have been misspelled - different passphrases generate different wallets, passphrase is case-sensitive). " "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) + ", wallet address: " + m_account.get_public_address_str(m_nettype)); @@ -4581,18 +4569,12 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip void wallet2::encrypt_keys(const crypto::chacha_key &key) { - boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); - if (--m_decrypt_keys_lockers) // another lock left ? - return; m_account.encrypt_keys(key); m_account.decrypt_viewkey(key); } void wallet2::decrypt_keys(const crypto::chacha_key &key) { - boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); - if (m_decrypt_keys_lockers++) // already unlocked ? - return; m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -4768,7 +4750,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); - m_account.finalize_multisig(spend_public_key); // Not possible to restore a multisig wallet that is able to activate the MMS // (because the original keys are not (yet) part of the restore info), so @@ -4983,24 +4964,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p store(); } } - +//---------------------------------------------------------------------------------------------------- std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold) + const std::vector<std::string> &initial_kex_msgs, + const std::uint32_t threshold) { - CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); - CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); - CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - - std::string extra_multisig_info; - std::vector<crypto::secret_key> multisig_keys; - rct::key spend_pkey = rct::identity(); - rct::key spend_skey; - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(&spend_skey, sizeof(spend_skey));}); - std::vector<crypto::public_key> multisig_signers; - - // decrypt keys + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5008,104 +4977,88 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() + { + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); + } + ); } - // In common multisig scheme there are 4 types of key exchange rounds: - // 1. First round is exchange of view secret keys and public spend keys. - // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key, - // M - public multisig key (in first round it equals to public spend key), K - new public multisig key. - // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key, - // k - secret multisig key used to sign transactions. k and M are sets of keys, of course. - // And secret spend key as the sum of all participant's secret multisig keys - // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys - // and calculate common spend public key as sum of all unique participants' public multisig keys. - // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds. - - // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G! - // Wallet's public spend key is the sum of unique public multisig keys of all participants. - // secret_spend_key * G = public signer key - - if (threshold == spend_keys.size() + 1) - { - // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key - MINFO("Creating spend key..."); + // create multisig account + multisig::multisig_account multisig_account{ + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - // Calculates all multisig keys and spend key - cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); + // open initial kex messages, validate them, extract signers + std::vector<multisig::multisig_kex_msg> expanded_msgs; + std::vector<crypto::public_key> signers; + expanded_msgs.reserve(initial_kex_msgs.size()); + signers.reserve(initial_kex_msgs.size() + 1); - // Our signer key is b * G, where b is secret spend key. - multisig_signers = spend_keys; - multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key)); - } - else + for (const auto &msg : initial_kex_msgs) { - // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi). - // note that derivations are public keys as DH exchange suppose it to be - auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys); - - spend_pkey = rct::identity(); - multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey); - - if (threshold == spend_keys.size()) - { - // N - 1 / N case + expanded_msgs.emplace_back(msg); - // We need an extra step, so we package all the composite public keys - // we know about, and make a signed string out of them - MINFO("Creating spend key..."); + // validate each message + // 1. must be 'round 1' + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1, + "Trying to make multisig with message that has invalid multisig kex round (should be '1')."); - // Calculating set of our secret multisig keys as follows: mi = H(Mi), - // where mi - secret multisig key, Mi - others' participants public multisig key - multisig_keys = cryptonote::calculate_multisig_keys(derivations); + // 2. duplicate signers not allowed + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(), + "Duplicate signers not allowed when converting a wallet to multisig."); - // calculating current participant's spend secret key as sum of all secret multisig keys for current participant. - // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend! - // Entire wallet's secret spend is sum of all unique secret multisig keys - // among all of participants and is not held by anyone! - spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys)); + // add signer (skip self for now) + if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey()) + signers.push_back(expanded_msgs.back().get_signing_pubkey()); + } - // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey)); - } - else - { - // M / N case - MINFO("Preparing keys for next exchange round..."); + // add self to signers + signers.push_back(multisig_account.get_base_pubkey()); - // Preparing data for middle round - packing new public multisig keys to exchage with others. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key); - spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key); + // intialize key exchange + multisig_account.initialize_kex(threshold, signers, expanded_msgs); + CHECK_AND_ASSERT_THROW_MES(multisig_account.account_is_active(), "Failed to activate multisig account."); - // Need to store middle keys to be able to proceed in case of wallet shutdown. - m_multisig_derivations = derivations; - } - } - + // update wallet state if (!m_original_keys_available) { // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages // (making a wallet multisig overwrites those keys, see account_base::make_multisig) - m_original_address = m_account.get_keys().m_account_address; - m_original_view_secret_key = m_account.get_keys().m_view_secret_key; + m_original_address = get_account().get_keys().m_account_address; + m_original_view_secret_key = get_account().get_keys().m_view_secret_key; m_original_keys_available = true; } clear(); - MINFO("Creating view key..."); - crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); + // account base MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), - "Failed to create multisig wallet due to bad keys"); - memwipe(&spend_skey, sizeof(rct::key)); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to create multisig wallet account due to bad keys"); init_type(hw::device::device_type::SOFTWARE); m_original_keys_available = true; m_multisig = true; m_multisig_threshold = threshold; - m_multisig_signers = multisig_signers; - ++m_multisig_rounds_passed; + m_multisig_signers = signers; + m_multisig_rounds_passed = 1; + + // derivations stored (should be empty in last round) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5118,42 +5071,18 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, if (!m_wallet_file.empty()) store(); - return extra_multisig_info; -} - -std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info) -{ - THROW_WALLET_EXCEPTION_IF(info.empty(), - error::wallet_internal_error, "Empty multisig info"); - - if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - THROW_WALLET_EXCEPTION_IF(false, - error::wallet_internal_error, "Unsupported info string"); - } - - std::vector<crypto::public_key> signers; - std::unordered_set<crypto::public_key> pkeys; - - THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys), - error::wallet_internal_error, "Bad extra multisig info"); - - return exchange_multisig_keys(password, pkeys, signers); + return multisig_account.get_next_kex_round_msg(); } - +//---------------------------------------------------------------------------------------------------- std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> derivations, - std::vector<crypto::public_key> signers) + const std::vector<std::string> &kex_messages) { - CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys"); - CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers"); - - bool ready = false; + bool ready{false}; CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished"); + CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); - // keys are decrypted + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5161,37 +5090,70 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); - } - - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1) - { - // the last round is passed and we have to calculate spend public key - // add ours if not included - crypto::public_key local_signer = get_multisig_signer_public_key(); - - if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) - { - signers.push_back(local_signer); - for (const auto &msk: get_account().get_multisig_keys()) + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() { - derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); } - } + ); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const auto &msg : kex_messages) + expanded_msgs.emplace_back(msg); + + // reconstruct multisig account + multisig::multisig_keyset_map_memsafe_t kex_origins_map; + + for (const auto &derivation : m_multisig_derivations) + kex_origins_map[derivation]; + + multisig::multisig_account multisig_account{ + m_multisig_threshold, + m_multisig_signers, + get_account().get_keys().m_spend_secret_key, + crypto::null_skey, //base common privkey: not used + get_account().get_keys().m_multisig_keys, + get_account().get_keys().m_view_secret_key, + m_account_public_address.m_spend_public_key, + m_account_public_address.m_view_public_key, + m_multisig_rounds_passed, + std::move(kex_origins_map), + "" + }; - CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + // update multisig kex + multisig_account.kex_update(expanded_msgs); - // Summing all of unique public multisig keys to calculate common public spend key - crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - m_account_public_address.m_spend_public_key = spend_public_key; - m_account.finalize_multisig(spend_public_key); + // update wallet state - m_multisig_signers = signers; - std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)) < 0; }); + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); - ++m_multisig_rounds_passed; - m_multisig_derivations.clear(); + // account base + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to update multisig wallet account due to bad keys"); + + // derivations stored (should be empty in last round) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + // rounds passed + m_multisig_rounds_passed = multisig_account.get_kex_rounds_complete(); + + // why is this necessary? who knows... + if (multisig_account.multisig_is_ready()) + { // keys are encrypted again keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5213,270 +5175,28 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor if (!m_wallet_file.empty()) store(); - - return {}; } - // Below are either middle or secret spend key establishment rounds - - for (const auto& key: m_multisig_derivations) - derivations.erase(key); - - // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys. - auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - - std::string extra_multisig_info; - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last - { - // Next round is last therefore we are performing secret spend establishment round as described above. - MINFO("Creating spend key..."); - - // Calculating our secret multisig keys by hashing our public multisig keys. - auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end())); - // And summing it to get personal secret spend key - crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys); - - m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys); - - // Packing public multisig keys to exchange with others and calculate common public spend key in the last round - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey); - } - else - { - // This is just middle round - MINFO("Preparing keys for next exchange round..."); - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key); - m_multisig_derivations = new_derivations; - } - - ++m_multisig_rounds_passed; - + // wallet/file relationship if (!m_wallet_file.empty()) create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); - return extra_multisig_info; -} - -void wallet2::unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const -{ - // parse all multisig info - public_keys.resize(info.size()); - secret_keys.resize(info.size()); - for (size_t i = 0; i < info.size(); ++i) - { - THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), - error::wallet_internal_error, "Bad multisig info: " + info[i]); - } - - // remove duplicates - for (size_t i = 0; i < secret_keys.size(); ++i) - { - for (size_t j = i + 1; j < secret_keys.size(); ++j) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) - { - MDEBUG("Duplicate key found, ignoring"); - secret_keys[j] = secret_keys.back(); - public_keys[j] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --j; - } - } - } - - // people may include their own, weed it out - const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (secret_keys[i] == local_skey) - { - MDEBUG("Local key is present, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; - } - else - { - THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, - "Found local spend public key, but not local view secret key - something very weird"); - } - } -} - -std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold) -{ - std::vector<crypto::secret_key> secret_keys(info.size()); - std::vector<crypto::public_key> public_keys(info.size()); - unpack_multisig_info(info, public_keys, secret_keys); - return make_multisig(password, secret_keys, public_keys, threshold); -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers) -{ - bool ready; - uint32_t threshold, total; - if (!multisig(&ready, &threshold, &total)) - { - MERROR("This is not a multisig wallet"); - return false; - } - if (ready) - { - MERROR("This multisig wallet is already finalized"); - return false; - } - if (threshold + 1 != total) - { - MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead"); - return false; - } - exchange_multisig_keys(password, pkeys, signers); - return true; -} - -bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const -{ - // parse all multisig info - signers.resize(info.size(), crypto::null_pkey); - for (size_t i = 0; i < info.size(); ++i) - { - if (!verify_extra_multisig_info(info[i], pkeys, signers[i])) - { - return false; - } - } - - return true; -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info) -{ - std::unordered_set<crypto::public_key> public_keys; - std::vector<crypto::public_key> signers; - if (!unpack_extra_multisig_info(info, signers, public_keys)) - { - MERROR("Bad multisig info"); - return false; - } - - return finalize_multisig(password, public_keys, signers); -} - -std::string wallet2::get_multisig_info() const -{ - // It's a signed package of private view key and public spend key - const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - crypto::hash hash; - - std::string data; - data += std::string((const char *)&skey, sizeof(crypto::secret_key)); - data += std::string((const char *)&pkey, sizeof(crypto::public_key)); - - data.resize(data.size() + sizeof(crypto::signature)); - crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); - - return std::string("MultisigV1") + tools::base58::encode(data); -} - -bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) -{ - const size_t header_len = strlen("MultisigV1"); - if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(header_len), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - size_t offset = 0; - skey = *(const crypto::secret_key*)(decoded.data() + offset); - offset += sizeof(skey); - pkey = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(pkey); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, pkey, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - return true; + return multisig_account.get_next_kex_round_msg(); } - -bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer) +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_multisig_first_kex_msg() const { - if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); - size_t offset = 0; - signer = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(signer); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, signer, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - for (size_t n = 0; n < n_keys; ++n) - { - crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); - pkeys.insert(mspk); - offset += sizeof(mspk); - } + // create multisig account + multisig::multisig_account multisig_account{ + // k_base = H(normal private spend key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + // k_view = H(normal private view key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - return true; + return multisig_account.get_next_kex_round_msg(); } - +//---------------------------------------------------------------------------------------------------- bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const { if (!m_multisig) @@ -5486,10 +5206,13 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const if (total) *total = m_multisig_signers.size(); if (ready) - *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); + { + *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) && + (m_multisig_rounds_passed == multisig::multisig_kex_rounds_required(m_multisig_signers.size(), m_multisig_threshold) + 1); + } return true; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_multisig_partial_key_images() const { if (!m_multisig) @@ -5499,7 +5222,7 @@ bool wallet2::has_multisig_partial_key_images() const return true; return false; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_unknown_key_images() const { for (const auto &td: m_transfers) @@ -5714,13 +5437,14 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); //keys loaded ok! - //try to load wallet file. but even if we failed, it is not big problem - if (use_fs && (!boost::filesystem::exists(m_wallet_file, e) || e)) + //try to load wallet cache. but even if we failed, it is not big problem + bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty(); + if (cache_missing) { - LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); + LOG_PRINT_L0("wallet cache missing: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; } - else if (use_fs || !cache_buf.empty()) + else { wallet2::cache_file_data cache_file_data; std::string cache_file_buf; @@ -6130,6 +5854,19 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo amount_per_subaddr[0] = utx.second.m_change; else found->second += utx.second.m_change; + + // add transfers to same wallet + for (const auto &dest: utx.second.m_dests) { + auto index = get_subaddress_index(dest.addr); + if (index && (*index).major == index_major) + { + auto found = amount_per_subaddr.find((*index).minor); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[(*index).minor] = dest.amount; + else + found->second += dest.amount; + } + } } } @@ -6726,7 +6463,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device())); } - txs.transfers = export_outputs(); + txs.new_transfers = export_outputs(); // save as binary std::ostringstream oss; binary_archive<true> ar(oss); @@ -6867,7 +6604,10 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { - import_outputs(exported_txs.transfers); + if (!exported_txs.new_transfers.second.empty()) + import_outputs(exported_txs.new_transfers); + else + import_outputs(exported_txs.transfers); // sign the transactions for (size_t n = 0; n < exported_txs.txes.size(); ++n) @@ -6880,8 +6620,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin rct::RCTConfig rct_config = sd.rct_config; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, sd.use_view_tags); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -6966,16 +6705,17 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin for (size_t i = 0; i < tx.vout.size(); ++i) { - if (tx.vout[i].target.type() != typeid(cryptonote::txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[i], output_public_key)) continue; - const cryptonote::txout_to_key &out = boost::get<cryptonote::txout_to_key>(tx.vout[i].target); + // if this output is back to this wallet, we can calculate its key image already - if (!is_out_to_acc_precomp(m_subaddresses, out.key, derivation, additional_derivations, i, hwdev)) + if (!is_out_to_acc_precomp(m_subaddresses, output_public_key, derivation, additional_derivations, i, hwdev, get_output_view_tag(tx.vout[i]))) continue; crypto::key_image ki; cryptonote::keypair in_ephemeral; - if (generate_key_image_helper(keys, m_subaddresses, out.key, tx_pub_key, additional_tx_pub_keys, i, in_ephemeral, ki, hwdev)) - signed_txes.tx_key_images[out.key] = ki; + if (generate_key_image_helper(keys, m_subaddresses, output_public_key, tx_pub_key, additional_tx_pub_keys, i, in_ephemeral, ki, hwdev)) + signed_txes.tx_key_images[output_public_key] = ki; else MERROR("Failed to calculate key image"); } @@ -7077,7 +6817,6 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal bool wallet2::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::string s = signed_tx_st; - boost::system::error_code errcode; signed_tx_set signed_txs; const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1; @@ -7389,76 +7128,113 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto txids.clear(); - // sign the transactions + // The 'exported_txs' contains a set of different transactions for the multisig group to try to sign. Each of those + // transactions has a set of 'signing attempts' corresponding to all the possible signing groups within the multisig. + // - Here, we will partially sign as many of those signing attempts as possible, for each proposed transaction. for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; THROW_WALLET_EXCEPTION_IF(ptx.multisig_sigs.empty(), error::wallet_internal_error, "No signatures found in multisig tx"); - tools::wallet2::tx_construction_data &sd = ptx.construction_data; - LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) << + const tools::wallet2::tx_construction_data &sd = ptx.construction_data; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << (sd.sources[0].outputs.size()) << ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); - cryptonote::transaction tx; - rct::multisig_out msout = ptx.multisig_sigs.front().msout; - auto sources = sd.sources; - rct::RCTConfig rct_config = sd.rct_config; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, false); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), - error::wallet_internal_error, "Transaction prefix does not match data"); - - // Tests passed, sign - std::vector<unsigned int> indices; - for (const auto &source: sources) - indices.push_back(source.real_output); + // reconstruct the partially-signed transaction attempt to verify we are signing something that at least looks like a transaction + // note: the caller should further verify that the tx details are acceptable (inputs/outputs/memos/tx type) + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init( + m_account.get_keys(), + ptx.construction_data.extra, + ptx.construction_data.unlock_time, + ptx.construction_data.subaddr_account, + ptx.construction_data.subaddr_indices, + ptx.construction_data.sources, + ptx.construction_data.splitted_dsts, + ptx.construction_data.change_dts, + ptx.construction_data.rct_config, + ptx.construction_data.use_rct, + true, //true = we are reconstructing the tx (it was first constructed by the tx proposer) + ptx.tx_key, + ptx.additional_tx_keys, + ptx.tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + // go through each signing attempt for this transaction (each signing attempt corresponds to some subgroup of signers + // of size 'threshold') for (auto &sig: ptx.multisig_sigs) { + // skip this partial tx if it's intended for a subgroup of signers that doesn't include the local signer + // note: this check can only weed out signers who provided multisig_infos to the multisig tx proposer's + // (initial author's) last call to import_multisig() before making this tx proposal; all other signers + // will encounter a 'need to export multisig' wallet error in get_multisig_k() below + // note2: the 'need to export multisig' wallet error can also appear if a bad/buggy tx proposer adds duplicate + // 'used_L' to the set of tx attempts, or if two different tx proposals use the same 'used_L' values and the + // local signer calls this function on both of them if (sig.ignore.find(local_signer) == sig.ignore.end()) { - ptx.tx.rct_signatures = sig.sigs; - - rct::keyV k; + rct::keyM local_nonces_k(sd.selected_transfers.size(), rct::keyV(multisig::signing::kAlphaComponents)); rct::key skey = rct::zero(); - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){ memwipe(k.data(), k.size() * sizeof(k[0])); memwipe(&skey, sizeof(skey)); }); - - for (size_t idx: sd.selected_transfers) - k.push_back(get_multisig_k(idx, sig.used_L)); + auto wiper = epee::misc_utils::create_scope_leave_handler([&]{ + for (auto& e: local_nonces_k) + memwipe(e.data(), e.size() * sizeof(rct::key)); + memwipe(&skey, sizeof(rct::key)); + }); + + // get local signer's nonces for this transaction attempt's inputs + // note: whoever created 'exported_txs' has full power to match proposed tx inputs (selected_transfers) + // with the public nonces of the multisig signers who call this function (via 'used_L' as identifiers), however + // the local signer will only use a given nonce exactly once (even if a used_L is repeated) + for (std::size_t i = 0; i < local_nonces_k.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + get_multisig_k(sd.selected_transfers[i], sig.used_L, local_nonces_k[i][j]); + } + } - for (const auto &msk: get_account().get_multisig_keys()) + // round-robin signing: sign with all local multisig key shares that other signers have not signed with yet + for (const auto &multisig_skey: get_account().get_multisig_keys()) { - crypto::public_key pmsk = get_multisig_signing_public_key(msk); + crypto::public_key multisig_pkey = get_multisig_signing_public_key(multisig_skey); - if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + if (sig.signing_keys.find(multisig_pkey) == sig.signing_keys.end()) { - sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); - sig.signing_keys.insert(pmsk); + sc_add(skey.bytes, skey.bytes, rct::sk2rct(multisig_skey).bytes); + sig.signing_keys.insert(multisig_pkey); } } - THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, sig.msout, skey), - error::wallet_internal_error, "Failed signing, transaction likely malformed"); - sig.sigs = ptx.tx.rct_signatures; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, local_nonces_k, skey, sig.c_0, sig.s), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::next_partial_sign" + ); } } const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; if (is_last) { - // when the last signature on a multisig tx is made, we select the right - // signature to plug into the final tx + // if there are signatures from enough signers (assuming the local signer signed 1+ tx attempts), find the tx + // attempt with a full set of signatures so this tx can be finalized bool found = false; for (const auto &sig: ptx.multisig_sigs) { if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers)) { THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); - ptx.tx.rct_signatures = sig.sigs; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(ptx.construction_data.sources, sig.c_0, sig.s, ptx.tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); found = true; } } THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, - "Final signed transaction not found: this transaction was likely made without our export data, so we cannot sign it"); + "Unable to finalize the transaction: the ignore sets for these tx attempts seem to be malformed."); const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { @@ -7469,7 +7245,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto } } - // txes generated, get rid of used k values + // signatures generated, get rid of any unused k values (must do export_multisig() to make more tx attempts with the + // 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])); @@ -7501,17 +7278,17 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags, uint64_t base_fee, uint64_t fee_quantization_mask) const { if (use_per_byte_fee) { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); - return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_quantization_mask); } else { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); - return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + return calculate_fee(base_fee, estimated_tx_size); } } @@ -7584,6 +7361,40 @@ uint64_t wallet2::get_base_fee() return get_dynamic_base_fee_estimate(); } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_base_fee(uint32_t priority) +{ + const bool use_2021_scaling = use_fork_rules(HF_VERSION_2021_SCALING, -30 * 1); + if (use_2021_scaling) + { + // clamp and map to 0..3 indices, mapping 0 (default, but should not end up here) to 0, and 1..4 to 0..3 + if (priority == 0) + priority = 1; + else if (priority > 4) + priority = 4; + --priority; + + std::vector<uint64_t> fees; + boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_base_fee_estimate_2021_scaling(FEE_ESTIMATE_GRACE_BLOCKS, fees); + if (result) + { + MERROR("Failed to determine base fee, using default"); + return FEE_PER_BYTE; + } + if (priority >= fees.size()) + { + MERROR("Failed to determine base fee for priority " << priority << ", using default"); + return FEE_PER_BYTE; + } + return fees[priority]; + } + else + { + const uint64_t base_fee = get_base_fee(); + const uint64_t fee_multiplier = get_fee_multiplier(priority); + return base_fee * fee_multiplier; + } +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_quantization_mask() { if(m_light_wallet) @@ -7615,6 +7426,8 @@ int wallet2::get_fee_algorithm() //------------------------------------------------------------------------------------------------------------------------------ uint64_t wallet2::get_min_ring_size() { + if (use_fork_rules(HF_VERSION_MIN_MIXIN_15, 0)) + return 16; if (use_fork_rules(8, 10)) return 11; if (use_fork_rules(7, 10)) @@ -7628,6 +7441,8 @@ uint64_t wallet2::get_min_ring_size() //------------------------------------------------------------------------------------------------------------------------------ uint64_t wallet2::get_max_ring_size() { + if (use_fork_rules(HF_VERSION_MIN_MIXIN_15, 0)) + return 16; if (use_fork_rules(8, 10)) return 11; return 0; @@ -7658,9 +7473,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority) { // check if there's a backlog in the tx pool const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(1); - const double fee_level = fee_multiplier * base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024)); + const uint64_t base_fee = get_base_fee(1); + const double fee_level = base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024)); const std::vector<std::pair<uint64_t, uint64_t>> blocks = estimate_backlog({std::make_pair(fee_level, fee_level)}); if (blocks.size() != 1) { @@ -7809,6 +7623,14 @@ bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &k catch (const std::exception &e) { return false; } } +bool wallet2::get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->get_rings(key, key_images, outs); } + catch (const std::exception &e) { return false; } +} + bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs) { for (auto i: m_confirmed_txs) @@ -7847,6 +7669,15 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin catch (const std::exception &e) { return false; } } +bool wallet2::set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative) +{ + if (!m_ringdb) + return false; + + try { return m_ringdb->set_rings(get_ringdb_key(), rings, relative); } + catch (const std::exception &e) { return false; } +} + bool wallet2::unset_ring(const std::vector<crypto::key_image> &key_images) { if (!m_ringdb) @@ -8021,7 +7852,7 @@ bool wallet2::is_keys_file_locked() const return m_keys_file_locker->locked(); } -bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const +bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const { if (!unlocked) // don't add locked outs return false; @@ -8032,16 +7863,18 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; // check the keys are valid - if (!rct::isInMainSubgroup(rct::pk2rct(output_public_key))) + if (valid_public_keys_cache.find(output_public_key) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(rct::pk2rct(output_public_key))) { MWARNING("Key " << output_public_key << " at index " << global_index << " is not in the main subgroup"); return false; } - if (!rct::isInMainSubgroup(mask)) + valid_public_keys_cache.insert(output_public_key); + if (valid_public_keys_cache.find(rct::rct2pk(mask)) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(mask)) { MWARNING("Commitment " << mask << " at index " << global_index << " is not in the main subgroup"); return false; } + valid_public_keys_cache.insert(rct::rct2pk(mask)); // if (is_output_blackballed(output_public_key)) // don't add blackballed outputs // return false; outs.back().push_back(item); @@ -8074,7 +7907,6 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); - size_t n_outs = 0; for (const auto &e: ores.amount_outs) n_outs += e.outputs.size(); } // Check if we got enough outputs for each amount @@ -8085,6 +7917,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ MDEBUG("selected transfers size: " << selected_transfers.size()); + std::unordered_set<crypto::public_key> valid_public_keys_cache; for(size_t idx: selected_transfers) { // Create new index @@ -8136,7 +7969,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false)) rct_commit = rct::zeroCommit(td.amount()); - if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) { + if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true, valid_public_keys_cache)) { MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key); MDEBUG("index " << global_index); } @@ -8173,12 +8006,12 @@ std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector< return std::make_pair(std::move(unique), total); } -void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache) { std::vector<uint64_t> rct_offsets; for (size_t attempts = 3; attempts > 0; --attempts) { - get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets); + get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets, valid_public_keys_cache); if (!rct) return; @@ -8200,7 +8033,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed")); } -void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -8244,6 +8077,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); // request histogram for all outputs, except 0 if we have the rct distribution + req_t.amounts.reserve(selected_transfers.size()); for(size_t idx: selected_transfers) if (!m_transfers[idx].is_rct() || !has_rct_distribution) req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); @@ -8271,6 +8105,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t); + req_t.amounts.reserve(req_t.amounts.size() + selected_transfers.size()); for(size_t idx: selected_transfers) req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); std::sort(req_t.amounts.begin(), req_t.amounts.end()); @@ -8317,6 +8152,25 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } } + std::vector<crypto::key_image> ring_key_images; + ring_key_images.reserve(selected_transfers.size()); + std::unordered_map<crypto::key_image, std::vector<uint64_t>> existing_rings; + for(size_t idx: selected_transfers) + { + const transfer_details &td = m_transfers[idx]; + if (td.m_key_image_known && !td.m_key_image_partial) + ring_key_images.push_back(td.m_key_image); + } + if (!ring_key_images.empty()) + { + std::vector<std::vector<uint64_t>> all_outs; + if (get_rings(get_ringdb_key(), ring_key_images, all_outs)) + { + for (size_t i = 0; i < ring_key_images.size(); ++i) + existing_rings[ring_key_images[i]] = std::move(all_outs[i]); + } + } + // we ask for more, to have spares if some outputs are still locked size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1); LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count); @@ -8330,6 +8184,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> gamma.reset(new gamma_picker(rct_offsets)); size_t num_selected_transfers = 0; + req.outputs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)); + daemon_resp.outs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)); for(size_t idx: selected_transfers) { ++num_selected_transfers; @@ -8439,9 +8295,12 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // if we have a known ring, use it if (td.m_key_image_known && !td.m_key_image_partial) { - std::vector<uint64_t> ring; - if (get_ring(get_ringdb_key(), td.m_key_image, ring)) + + const auto it = existing_rings.find(td.m_key_image); + const bool has_ring = it != existing_rings.end(); + if (has_ring) { + const std::vector<uint64_t> &ring = it->second; MINFO("This output has a known ring, reusing (size " << ring.size() << ")"); THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error, "An output in this transaction was previously spent on another chain with ring size " + @@ -8641,7 +8500,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::request chunk_req = AUTO_VAL_INIT(chunk_req); COMMAND_RPC_GET_OUTPUTS_BIN::response chunk_daemon_resp = AUTO_VAL_INIT(chunk_daemon_resp); chunk_req.get_txid = false; - for (size_t i = 0; i < std::min<size_t>(req.outputs.size() - offset, chunk_size); ++i) + const size_t this_chunk_size = std::min<size_t>(req.outputs.size() - offset, chunk_size); + chunk_req.outputs.reserve(this_chunk_size); + for (size_t i = 0; i < this_chunk_size; ++i) chunk_req.outputs.push_back(req.outputs[offset + i]); const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; @@ -8697,7 +8558,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { size_t i = base + n; if (req.outputs[i].index == td.m_global_output_index) - if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) + if (daemon_resp.outs[i].key == td.get_public_key()) if (daemon_resp.outs[i].mask == mask) if (daemon_resp.outs[i].unlocked) real_out_found = true; @@ -8706,14 +8567,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> "Daemon response did not include the requested real output"); // pick real out first (it will be sorted when done) - outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); + outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask)); // then pick outs from an existing ring, if any if (td.m_key_image_known && !td.m_key_image_partial) { - std::vector<uint64_t> ring; - if (get_ring(get_ringdb_key(), td.m_key_image, ring)) + const auto it = existing_rings.find(td.m_key_image); + if (it != existing_rings.end()) { + const std::vector<uint64_t> &ring = it->second; for (uint64_t out: ring) { if (out < num_outs) @@ -8727,7 +8589,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (req.outputs[i].index == out) { 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 << " (from existing ring)"); - 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); + 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); found = true; break; } @@ -8752,7 +8614,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { size_t i = base + order[o]; 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); + 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); } if (outs.back().size() < fake_outputs_count + 1) { @@ -8780,6 +8642,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } // save those outs in the ringdb for reuse + std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings; + rings.reserve(selected_transfers.size()); for (size_t i = 0; i < selected_transfers.size(); ++i) { const size_t idx = selected_transfers[i]; @@ -8789,15 +8653,17 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> ring.reserve(outs[i].size()); for (const auto &e: outs[i]) ring.push_back(std::get<0>(e)); - if (!set_ring(td.m_key_image, ring, false)) - MERROR("Failed to set ring for " << td.m_key_image); + rings.push_back(std::make_pair(td.m_key_image, std::move(ring))); } + if (!set_rings(rings, false)) + MERROR("Failed to set rings"); } template<typename T> void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, - std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, + bool use_view_tags) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -8833,7 +8699,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts"); if (outs.empty()) - get_outs(outs, selected_transfers, fake_outputs_count, false); // may throw + get_outs(outs, selected_transfers, fake_outputs_count, false, valid_public_keys_cache); // may throw //prepare inputs LOG_PRINT_L2("preparing outputs"); @@ -8870,7 +8736,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.dest = rct::pk2rct(td.get_public_key()); real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); @@ -8906,9 +8772,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8946,6 +8811,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = false; ptx.construction_data.rct_config = { rct::RangeProofBorromean, 0 }; + ptx.construction_data.use_view_tags = use_view_tags; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -8956,8 +8822,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, - std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config) + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -8989,6 +8855,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry // At this step we need to define set of participants available for signature, // i.e. those of them who exchanged with multisig info's + // note: The oldest unspent owned output's multisig info (in m_transfers) will contain the most recent result of + // 'import_multisig()', which means only 'fresh' multisig infos (public nonces) will be used to make tx attempts. + // - If a signer's info was missing from the latest call to 'import_multisig()', then they won't be able to participate! + // - If a newly-acquired output doesn't have enouch nonces from multisig infos, then it can't be spent! for (const crypto::public_key &signer: m_multisig_signers) { if (signer == local_signer) @@ -9050,13 +8920,12 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts"); if (outs.empty()) - get_outs(outs, selected_transfers, fake_outputs_count, all_rct); // may throw + get_outs(outs, selected_transfers, fake_outputs_count, all_rct, valid_public_keys_cache); // may throw //prepare inputs LOG_PRINT_L2("preparing outputs"); size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - std::unordered_set<rct::key> used_L; for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); @@ -9099,10 +8968,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry src.real_output_in_tx_index = td.m_internal_output_index; src.mask = td.m_mask; if (m_multisig) - { - auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L); - } + // note: multisig_kLRki is a legacy struct, currently only used as a key image shuttle into the multisig tx builder + src.multisig_kLRki = {.k = {}, .L = {}, .R = {}, .ki = rct::ki2rct(td.m_key_image)}; else src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); @@ -9139,12 +9006,41 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); auto sources_copy = sources; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + if (m_multisig) { + // prepare the core part of a multisig tx (many tx attempts for different signer groups can be spun off this core piece) + std::set<std::uint32_t> subaddr_minor_indices; + for (size_t idx: selected_transfers) { + subaddr_minor_indices.insert(m_transfers[idx].m_subaddr_index.minor); + } + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init(m_account.get_keys(), + extra, + unlock_time, + subaddr_account, + subaddr_minor_indices, + sources, + splitted_dsts, + change_dts, + rct_config, + true, + false, + tx_key, + additional_tx_keys, + tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + } + else { + // make a normal tx + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, use_view_tags); + LOG_PRINT_L2("constructed tx, r="<<r); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + } THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); // work out the permutation done on sources @@ -9162,41 +9058,77 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation"); std::vector<tools::wallet2::multisig_sig> multisig_sigs; - if (m_multisig) - { - auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout}); - - if (m_multisig_threshold < m_multisig_signers.size()) - { - const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx); - - // create the other versions, one for every other participant (the first one's already done above) - for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index) - { - std::unordered_set<rct::key> new_used_L; - size_t src_idx = 0; - THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes"); - for(size_t idx: selected_transfers) - { - cryptonote::tx_source_entry& src = sources_copy[src_idx]; - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L); - ++src_idx; + if (m_multisig) { + if (ignore_sets.empty()) + ignore_sets.emplace_back(); + const std::size_t num_multisig_attempts = ignore_sets.size(); + multisig_sigs.resize(num_multisig_attempts); + std::unordered_set<rct::key> all_used_L; + std::unordered_set<crypto::public_key> signing_keys; + for (const crypto::secret_key &multisig_skey: get_account().get_multisig_keys()) + signing_keys.insert(get_multisig_signing_public_key(multisig_skey)); + const std::size_t num_sources = sources.size(); + const std::size_t num_alpha_components = multisig::signing::kAlphaComponents; + + // initiate a multisig tx attempt for each unique set of signers that + // a) includes the local signer + // b) includes other signers who most recently sent the local signer LR public nonces via 'export_multisig() -> import_multisig()' + for (std::size_t i = 0; i < num_multisig_attempts; ++i) { + multisig_sig& sig = multisig_sigs[i]; + sig.total_alpha_G.resize(num_sources, rct::keyV(num_alpha_components)); + sig.total_alpha_H.resize(num_sources, rct::keyV(num_alpha_components)); + sig.s.resize(num_sources); + sig.c_0.resize(num_sources); + + // for each tx input, get public musig2-style nonces from + // a) temporary local-generated private nonces (used to make the local partial signatures on each tx attempt) + // b) other signers' public nonces, sent to the local signer via 'export_multisig() -> import_multisig()' + // - WARNING: If two multisig players initiate multisig tx attempts separately, but spend the same funds (and hence rely on the same LR public nonces), + // then if two signers partially sign different tx attempt sets, then all attempts that require both signers will become garbage, + // because LR nonces can only be used for one tx attempt. + for (std::size_t j = 0; j < num_sources; ++j) { + rct::keyV alpha(num_alpha_components); + auto alpha_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(alpha.data()), alpha.size() * sizeof(rct::key)); + }); + for (std::size_t m = 0; m < num_alpha_components; ++m) { + const rct::multisig_kLRki kLRki = get_multisig_composite_kLRki( + selected_transfers[ins_order[j]], + ignore_sets[i], + all_used_L, //collect all public L nonces used by this tx proposal (set of tx attempts) to avoid duplicates + sig.used_L //record the public L nonces used by this tx input to this tx attempt, for coordination with other signers + ); + alpha[m] = kLRki.k; + sig.total_alpha_G[j][m] = kLRki.L; + sig.total_alpha_H[j][m] = kLRki.R; } - LOG_PRINT_L2("Creating supplementary multisig transaction"); - cryptonote::transaction ms_tx; - auto sources_copy_copy = sources_copy; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, false); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix"); - multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout}); - - ms_tx.rct_signatures = tx.rct_signatures; - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures"); + // local signer: initial partial signature on this tx input for this tx attempt + // note: sign here with sender-receiver secret component, subaddress component, and ALL of the local signer's multisig key shares + // (this ultimately occurs deep in generate_key_image_helper_precomp()) + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.first_partial_sign(j, sig.total_alpha_G[j], sig.total_alpha_H[j], alpha, sig.c_0[j], sig.s[j]), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::first_partial_sign" + ); } + + // note: record the ignore set so when other signers go to add their signatures (sign_multisig_tx()), they + // can skip this tx attempt if they aren't supposed to sign it; this only works for signers who provided + // multisig_infos to the last 'import_multisig()' call by the local signer, all 'other signers' will encounter + // a 'need to export multisig_info' wallet error if they try to sign this partial tx, which means if they want to sign a tx + // they need to export_multisig() -> send to the local signer -> local signer calls import_multisig() with fresh + // multisig_infos from all signers -> local signer makes completely new tx attempts (or a different signer makes tx attempts) + sig.ignore = ignore_sets[i]; + sig.signing_keys = signing_keys; //the local signer signed with ALL of their multisig key shares, record their pubkeys for reference by other signers + } + if (m_multisig_threshold <= 1) { + // local signer: finish signing the tx inputs if we are the only signer (ignore all but the first 'attempt') + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(sources, multisig_sigs[0].c_0, multisig_sigs[0].s, tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); } } @@ -9231,9 +9163,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; ptx.construction_data.rct_config = { - tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, - use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 + rct::RangeProofPaddedBulletproof, + use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : 3 }; + ptx.construction_data.use_view_tags = use_fork_rules(get_view_tag_fork(), 0); ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -9873,7 +9806,7 @@ 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 accumulated_fee, accumulated_outputs, accumulated_change; + uint64_t accumulated_fee, accumulated_change; struct TX { std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; @@ -9927,14 +9860,16 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_rct = use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { - bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + std::unordered_set<crypto::public_key> valid_public_keys_cache; - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); + const uint64_t base_fee = get_base_fee(priority); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); // throw if attempting a transaction with no destinations @@ -9966,7 +9901,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // 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 = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag)); + 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)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9984,11 +9919,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Candidate subaddress index for spending: " << i); // determine threshold for fractional amount - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; - const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); // gather all dust and non-dust outputs belonging to specified subaddresses size_t num_nondust_outputs = 0; @@ -10063,7 +9998,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // start with an empty tx txes.push_back(TX()); accumulated_fee = 0; - accumulated_outputs = 0; accumulated_change = 0; adding_fee = false; needed_fee = 0; @@ -10076,13 +10010,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // the destination, and one for change. LOG_PRINT_L2("checking preferred"); std::vector<size_t> preferred_inputs; - uint64_t rct_outs_needed = 2 * (fake_outs_count + 1); - rct_outs_needed += 100; // some fudge factor since we don't know how many are locked if (use_rct) { // 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, base_fee, fee_multiplier, fee_quantization_mask); + 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); if (!preferred_inputs.empty()) { @@ -10182,7 +10114,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // add this output to the list to spend tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); - accumulated_outputs += available_amount; // clear any fake outs we'd already gathered, since we'll need a new set outs.clear(); @@ -10195,7 +10126,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // 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) << @@ -10212,7 +10143,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && + estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // 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)); @@ -10250,7 +10182,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); } @@ -10261,7 +10193,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee) { @@ -10309,13 +10241,13 @@ 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"); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + 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, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + 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_multiplier, fee_quantization_mask); + 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); 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)"); @@ -10334,13 +10266,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); while (needed_fee > test_ptx.fee) { if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + 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, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + 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_multiplier, fee_quantization_mask); + 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"); } @@ -10403,24 +10335,28 @@ skip_tx: 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, */ + valid_public_keys_cache, unlock_time, /* CONST uint64_t unlock_time, */ tx.needed_fee, /* CONST uint64_t fee, */ extra, /* const std::vector<uint8_t>& extra, */ test_tx, /* OUT cryptonote::transaction& tx, */ test_ptx, /* OUT cryptonote::transaction& tx, */ - rct_config); + rct_config, + use_view_tags); /* const bool use_view_tags */ } else { transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, + valid_public_keys_cache, unlock_time, tx.needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, - test_ptx); + test_ptx, + use_view_tags); } auto txBlob = t_serializable_object_to_blob(test_ptx.tx); tx.tx = test_tx; @@ -10521,14 +10457,16 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below // determine threshold for fractional amount const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + const uint64_t base_fee = get_base_fee(priority); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; - const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + std::unordered_set<crypto::public_key> valid_public_keys_cache; THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account"); @@ -10610,6 +10548,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton hw::device &hwdev = m_account.get_device(); boost::unique_lock<hw::device> hwdev_lock (hwdev); hw::reset_mode rst(hwdev); + std::unordered_set<crypto::public_key> valid_public_keys_cache; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { @@ -10631,13 +10570,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { - bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + const uint64_t base_fee = get_base_fee(priority); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); @@ -10663,12 +10603,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton uint64_t fee_dust_threshold; if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); - fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); + fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_quantization_mask); } else { - fee_dust_threshold = base_fee * fee_multiplier * (upper_transaction_weight_limit + 1023) / 1024; + fee_dust_threshold = base_fee * (upper_transaction_weight_limit + 1023) / 1024; } size_t idx = @@ -10694,7 +10634,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_weight_limit); - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); if (try_tx) { @@ -10702,7 +10642,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); // add N - 1 outputs for correct initial fee estimation for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) @@ -10711,13 +10651,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + 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, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + 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_multiplier, fee_quantization_mask); + 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; for (auto &dt: test_ptx.dests) available_for_fee += dt.amount; @@ -10748,13 +10688,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton dt.amount = dt_amount + dt_residue; } if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + 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, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + 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_multiplier, fee_quantization_mask); + 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); @@ -10787,11 +10727,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton cryptonote::transaction test_tx; pending_tx test_ptx; if (use_rct) { - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra, - test_tx, test_ptx, rct_config); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, + test_tx, test_ptx, rct_config, use_view_tags); } else { - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.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); tx.tx = test_tx; @@ -10860,7 +10800,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_ hw::wallet_shim wallet_shim; setup_shim(&wallet_shim, this); aux_data.tx_recipients = dsts_info; - aux_data.bp_version = (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1); + aux_data.bp_version = (use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1); aux_data.hard_fork = get_current_hard_fork(); dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data); tx_device_aux = aux_data.tx_device_aux; @@ -11081,7 +11021,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions() const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); - const uint64_t base_fee = get_base_fee(); + const uint64_t base_fee = get_base_fee(1); // may throw std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); @@ -11330,13 +11270,12 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string // derive the real output keypair const transfer_details& in_td = m_transfers[found->second]; - const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); - THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + crypto::public_key in_tx_out_pkey = in_td.get_public_key(); const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); keypair in_ephemeral; crypto::key_image in_img; - THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img, m_account.get_device()), + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img, m_account.get_device()), error::wallet_internal_error, "failed to generate key image"); THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); @@ -11535,24 +11474,12 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt for (size_t n = 0; n < tx.vout.size(); ++n) { - const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); - if (!out_key) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[n], output_public_key)) continue; - crypto::public_key derived_out_key; - bool r = crypto::derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); - bool found = out_key->key == derived_out_key; - crypto::key_derivation found_derivation = derivation; - if (!found && !additional_derivations.empty()) - { - r = crypto::derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); - found = out_key->key == derived_out_key; - found_derivation = additional_derivations[n]; - } - - if (found) + crypto::key_derivation found_derivation; + if (is_out_to_acc(address, output_public_key, derivation, additional_derivations, n, get_output_view_tag(tx.vout[n]), found_derivation)) { uint64_t amount; if (tx.version == 1 || tx.rct_signatures.type == rct::RCTTypeNull) @@ -11564,7 +11491,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::secret_key scalar1; crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); @@ -11644,6 +11571,42 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de } } +bool wallet2::is_out_to_acc(const cryptonote::account_public_address &address, const crypto::public_key& out_key, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const size_t output_index, const boost::optional<crypto::view_tag> &view_tag_opt, crypto::key_derivation &found_derivation) const +{ + crypto::public_key derived_out_key; + bool found = false; + bool r; + // first run quick check if output has matching view tag, otherwise output should not belong to account + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + // if view tag match, run slower check deriving output pub key and comparing to expected + r = crypto::derive_public_key(derivation, output_index, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); + if (out_key == derived_out_key) + { + found = true; + found_derivation = derivation; + } + } + + if (!found && !additional_derivations.empty()) + { + const crypto::key_derivation &additional_derivation = additional_derivations[output_index]; + if (out_can_be_to_acc(view_tag_opt, additional_derivation, output_index)) + { + r = crypto::derive_public_key(additional_derivation, output_index, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); + if (out_key == derived_out_key) + { + found = true; + found_derivation = additional_derivation; + } + } + } + + return found; +} + std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) { // fetch tx pubkey from the daemon @@ -12180,8 +12143,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); - const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target)); - THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found") + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[proof.index_in_tx], output_public_key), error::wallet_internal_error, "Output key wasn't found"); // get tx pub key const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); @@ -12196,7 +12159,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr return false; // check signature for key image - const std::vector<const crypto::public_key*> pubs = { &out_key->key }; + const std::vector<const crypto::public_key*> pubs = { &output_public_key }; ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig); if (!ok) return false; @@ -12205,7 +12168,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::key_derivation derivation; THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); crypto::public_key subaddr_spendkey; - crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(!crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, "The address doesn't seem to have received the fund"); @@ -12217,7 +12181,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::secret_key shared_secret; crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); amount = rct::h2d(ecdh_info.amount); } total += amount; @@ -12302,10 +12266,10 @@ uint64_t wallet2::get_approximate_blockchain_height() const const int seconds_per_block = DIFFICULTY_TARGET_V2; // Calculated blockchain height uint64_t approx_blockchain_height = fork_block + (time(NULL) - fork_time)/seconds_per_block; - // testnet got some huge rollbacks, so the estimation is way off - static const uint64_t approximate_testnet_rolled_back_blocks = 342100; - if (m_nettype == TESTNET && approx_blockchain_height > approximate_testnet_rolled_back_blocks) - approx_blockchain_height -= approximate_testnet_rolled_back_blocks; + // testnet and stagenet got some huge rollbacks, so the estimation is way off + static const uint64_t approximate_rolled_back_blocks = m_nettype == TESTNET ? 342100 : 30000; + if ((m_nettype == TESTNET || m_nettype == STAGENET) && approx_blockchain_height > approximate_rolled_back_blocks) + approx_blockchain_height -= approximate_rolled_back_blocks; LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); return approx_blockchain_height; } @@ -12659,11 +12623,7 @@ std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>> const transfer_details &td = m_transfers[n]; // get ephemeral public key - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - THROW_WALLET_EXCEPTION_IF(out.target.type() != typeid(txout_to_key), error::wallet_internal_error, - "Output is not txout_to_key"); - const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); - const crypto::public_key pkey = o.key; + const crypto::public_key pkey = td.get_public_key(); // get tx pub key std::vector<tx_extra_field> tx_extra_fields; @@ -12780,11 +12740,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const crypto::signature &signature = signed_key_images[n].second; // get ephemeral public key - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - THROW_WALLET_EXCEPTION_IF(out.target.type() != typeid(txout_to_key), error::wallet_internal_error, - "Non txout_to_key output found"); - const cryptonote::txout_to_key &o = boost::get<cryptonote::txout_to_key>(out.target); - const crypto::public_key pkey = o.key; + const crypto::public_key pkey = td.get_public_key(); if (!td.m_key_image_known || !(key_image == td.m_key_image)) { @@ -13138,10 +13094,10 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const +std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const { PERF_TIMER(export_outputs); - std::vector<tools::wallet2::transfer_details> outs; + std::vector<tools::wallet2::exported_transfer_details> outs; size_t offset = 0; if (!all) @@ -13153,7 +13109,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::expo { const transfer_details &td = m_transfers[n]; - outs.push_back(td); + exported_transfer_details etd; + etd.m_pubkey = td.get_public_key(); + etd.m_tx_pubkey = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); + etd.m_internal_output_index = td.m_internal_output_index; + etd.m_global_output_index = td.m_global_output_index; + etd.m_flags.flags = 0; + etd.m_flags.m_spent = td.m_spent; + etd.m_flags.m_frozen = td.m_frozen; + etd.m_flags.m_rct = td.m_rct; + etd.m_flags.m_key_image_known = td.m_key_image_known; + etd.m_flags.m_key_image_request = td.m_key_image_request; + etd.m_flags.m_key_image_partial = td.m_key_image_partial; + etd.m_amount = td.m_amount; + etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + + outs.push_back(etd); } return std::make_pair(offset, outs); @@ -13225,9 +13196,7 @@ process: THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(), error::wallet_internal_error, "Internal index is out of range"); - THROW_WALLET_EXCEPTION_IF(td.m_tx.vout[td.m_internal_output_index].target.type() != typeid(cryptonote::txout_to_key), - error::wallet_internal_error, "Unsupported output type"); - const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + crypto::public_key out_key = td.get_public_key(); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13246,6 +13215,93 @@ process: return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) +{ + PERF_TIMER(import_outputs); + + THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + "Imported outputs omit more outputs that we know of. Try using export_outputs all."); + + const size_t offset = outputs.first; + const size_t original_size = m_transfers.size(); + m_transfers.resize(offset + outputs.second.size()); + for (size_t i = 0; i < offset; ++i) + m_transfers[i].m_key_image_request = false; + for (size_t i = 0; i < outputs.second.size(); ++i) + { + exported_transfer_details etd = outputs.second[i]; + transfer_details &td = m_transfers[i + offset]; + + // setup td with "cheao" loaded data + td.m_block_height = 0; + td.m_txid = crypto::null_hash; + td.m_global_output_index = etd.m_global_output_index; + td.m_spent = etd.m_flags.m_spent; + td.m_frozen = etd.m_flags.m_frozen; + td.m_spent_height = 0; + td.m_mask = rct::identity(); + td.m_amount = etd.m_amount; + td.m_rct = etd.m_flags.m_rct; + td.m_key_image_known = etd.m_flags.m_key_image_known; + td.m_key_image_request = etd.m_flags.m_key_image_request; + td.m_key_image_partial = false; + + // skip those we've already imported, or which have different data + if (i + offset < original_size) + { + bool needs_processing = false; + if (!td.m_key_image_known) + needs_processing = true; + else if (!(etd.m_internal_output_index == td.m_internal_output_index)) + needs_processing = true; + else if (!(etd.m_pubkey == td.get_public_key())) + needs_processing = true; + + if (!needs_processing) + continue; + } + + // construct a synthetix tx prefix that has the info we'll need: the output with its pubkey, the tx pubkey in extra + td.m_tx = {}; + + THROW_WALLET_EXCEPTION_IF(etd.m_internal_output_index >= 65536, error::wallet_internal_error, "internal output index seems outrageously high, rejecting"); + td.m_internal_output_index = etd.m_internal_output_index; + cryptonote::txout_to_key tk; + tk.key = etd.m_pubkey; + cryptonote::tx_out out; + out.amount = etd.m_amount; + out.target = tk; + td.m_tx.vout.resize(etd.m_internal_output_index); + td.m_tx.vout.push_back(out); + + td.m_pk_index = 0; + add_tx_pub_key_to_extra(td.m_tx, etd.m_tx_pubkey); + if (!etd.m_additional_tx_keys.empty()) + add_additional_tx_pub_keys_to_extra(td.m_tx.extra, etd.m_additional_tx_keys); + + // the hot wallet wouldn't have known about key images (except if we already exported them) + cryptonote::keypair in_ephemeral; + + const crypto::public_key &tx_pub_key = etd.m_tx_pubkey; + const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys; + const crypto::public_key& out_key = etd.m_pubkey; + bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + if (should_expand(td.m_subaddr_index)) + expand_subaddresses(td.m_subaddr_index); + td.m_key_image_known = true; + td.m_key_image_request = true; + td.m_key_image_partial = false; + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset)); + + m_key_images[td.m_key_image] = i + offset; + m_pub_keys[td.get_public_key()] = i + offset; + } + + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { PERF_TIMER(import_outputs_from_str); @@ -13284,10 +13340,23 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) try { std::string body(data, headerlen); - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + + std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; + if (::serialization::serialize(ar, new_outputs)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } + catch (...) {} + if (!loaded) + new_outputs.second.clear(); + + std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + if (!loaded) try + { + binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; if (::serialization::serialize(ar, outputs)) if (::serialization::check_stream_state(ar)) loaded = true; @@ -13313,7 +13382,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) outputs.second = {}; } - imported_outputs = import_outputs(outputs); + imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs); } catch (const std::exception &e) { @@ -13323,13 +13392,6 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) return imported_outputs; } //---------------------------------------------------------------------------------------------------- -crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const -{ - crypto::public_key pkey; - crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey); - return pkey; -} -//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signer_public_key() const { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); @@ -13353,19 +13415,26 @@ crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const return get_multisig_signing_public_key(get_account().get_multisig_keys()[idx]); } //---------------------------------------------------------------------------------------------------- -rct::key wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const +void wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "idx out of range"); - for (const auto &k: m_transfers[idx].m_multisig_k) + for (auto &k: m_transfers[idx].m_multisig_k) { + if (k == rct::zero()) + continue; + + // decide whether or not to return a nonce just based on if its pubkey 'L = k*G' is attached to the transfer 'idx' rct::key L; rct::scalarmultBase(L, k); if (used_L.find(L) != used_L.end()) - return k; + { + nonce = k; + memwipe(static_cast<rct::key *>(&k), sizeof(rct::key)); //CRITICAL: a nonce may only be used once! + return; + } } THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed); - return rct::zero(); } //---------------------------------------------------------------------------------------------------- rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const @@ -13373,7 +13442,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); rct::multisig_kLRki kLRki; kLRki.k = k; - cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); + multisig::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image); return kLRki; } @@ -13420,7 +13489,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); + bool r = multisig::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -13431,19 +13500,27 @@ cryptonote::blobdata wallet2::export_multisig() const crypto::public_key signer = get_multisig_signer_public_key(); + // For each transfer (output owned by the multisig wallet): + // 1) Record the output's partial key image (from the local signer), so other signers can assemble the output's full key image. + // 2) Prepare enough signing nonces for one signing attempt with each possible combination of 'threshold' signers + // from the multisig group (only groups that include the local signer). + // - Calling this function will reset any nonces recorded by the previous call to this function. Doing so will + // invalidate any in-progress signing attempts that rely on the previous output of this function. info.resize(m_transfers.size()); for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; crypto::key_image ki; - memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + if (td.m_multisig_k.size()) + memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); + // record the partial key images for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); + bool r = multisig::generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } @@ -13452,6 +13529,15 @@ cryptonote::blobdata wallet2::export_multisig() // if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left. // That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1). size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1); + + // 'td.m_multisig_k' is an expansion of [{alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}], + // - A '{alpha_0, alpha_1, ...}' tuple contains a set of 'kAlphaComponents' nonces, which can be used for one + // signing attempt. Each output will gain 'nlr' tuples, so that every signing group can make one signing attempt. + // - All tuples are always cleared after 1+ of them is used to sign a tx attempt (in sign_multisig_tx()), so + // in practice, a call to this function only allows _one_ multisig signing cycle for each output (which can + // include signing attempts for multiple signer groups). + nlr *= multisig::signing::kAlphaComponents; + for (size_t m = 0; m < nlr; ++m) { td.m_multisig_k.push_back(rct::skGen()); @@ -13734,12 +13820,8 @@ std::string wallet2::make_uri(const std::string &address, const std::string &pay if (!payment_id.empty()) { - crypto::hash pid32; - if (!wallet2::parse_long_payment_id(payment_id, pid32)) - { - error = "Invalid payment id"; - return std::string(); - } + error = "Standalone payment id deprecated, use integrated address instead"; + return std::string(); } std::string uri = "monero:" + address; @@ -14043,43 +14125,6 @@ uint64_t wallet2::get_segregation_fork_height() const if (m_segregation_height > 0) return m_segregation_height; - if (m_use_dns && !m_offline) - { - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector<std::string> dns_urls = { - "segheights.moneropulse.org", - "segheights.moneropulse.net", - "segheights.moneropulse.co", - "segheights.moneropulse.se" - }; - - const uint64_t current_height = get_blockchain_current_height(); - uint64_t best_diff = std::numeric_limits<uint64_t>::max(), best_height = 0; - std::vector<std::string> records; - if (tools::dns_utils::load_txt_records_from_dns(records, dns_urls)) - { - for (const auto& record : records) - { - std::vector<std::string> fields; - boost::split(fields, record, boost::is_any_of(":")); - if (fields.size() != 2) - continue; - uint64_t height; - if (!string_tools::get_xtype_from_string(height, fields[1])) - continue; - - MINFO("Found segregation height via DNS: " << fields[0] << " fork height at " << height); - uint64_t diff = height > current_height ? height - current_height : current_height - height; - if (diff < best_diff) - { - best_diff = diff; - best_height = height; - } - } - if (best_height) - return best_height; - } - } return SEGREGATION_FORK_HEIGHT; } //---------------------------------------------------------------------------------------------------- @@ -14362,9 +14407,11 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i n_outputs = 2; // extra dummy output const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); - size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); return std::make_pair(size, weight); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 4167165fe..45cb5fae0 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -137,7 +137,7 @@ private: public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} @@ -347,7 +347,14 @@ private: bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } - const crypto::public_key &get_public_key() const { return boost::get<const cryptonote::txout_to_key>(m_tx.vout[m_internal_output_index].target).key; } + const crypto::public_key get_public_key() const { + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, + error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), + error::wallet_internal_error, "Unable to get output public key from output"); + return output_public_key; + }; BEGIN_SERIALIZE_OBJECT() FIELD(m_block_height) @@ -373,6 +380,40 @@ private: END_SERIALIZE() }; + struct exported_transfer_details + { + crypto::public_key m_pubkey; + uint64_t m_internal_output_index; + uint64_t m_global_output_index; + crypto::public_key m_tx_pubkey; + union + { + struct + { + uint8_t m_spent: 1; + uint8_t m_frozen: 1; + uint8_t m_rct: 1; + uint8_t m_key_image_known: 1; + uint8_t m_key_image_request: 1; // view wallets: we want to request it; cold wallets: it was requested + uint8_t m_key_image_partial: 1; + }; + uint8_t flags; + } m_flags; + uint64_t m_amount; + std::vector<crypto::public_key> m_additional_tx_keys; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(m_pubkey) + VARINT_FIELD(m_internal_output_index) + VARINT_FIELD(m_global_output_index) + FIELD(m_tx_pubkey) + FIELD(m_flags.flags) + VARINT_FIELD(m_amount) + FIELD(m_additional_tx_keys) + END_SERIALIZE() + }; + typedef std::vector<uint64_t> amounts_container; struct payment_details { @@ -498,10 +539,21 @@ private: uint64_t unlock_time; bool use_rct; rct::RCTConfig rct_config; + bool use_view_tags; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer + enum construction_flags_ : uint8_t + { + _use_rct = 1 << 0, // 00000001 + _use_view_tags = 1 << 1 // 00000010 + // next flag = 1 << 2 // 00000100 + // ... + // final flag = 1 << 7 // 10000000 + }; + uint8_t construction_flags; + BEGIN_SERIALIZE_OBJECT() FIELD(sources) FIELD(change_dts) @@ -509,7 +561,26 @@ private: FIELD(selected_transfers) FIELD(extra) FIELD(unlock_time) - FIELD(use_rct) + + // converted `use_rct` field into construction_flags when view tags + // were introduced to maintain backwards compatibility + if (!typename Archive<W>::is_saving()) + { + FIELD_N("use_rct", construction_flags) + use_rct = (construction_flags & _use_rct) > 0; + use_view_tags = (construction_flags & _use_view_tags) > 0; + } + else + { + construction_flags = 0; + if (use_rct) + construction_flags ^= _use_rct; + if (use_view_tags) + construction_flags ^= _use_view_tags; + + FIELD_N("use_rct", construction_flags) + } + FIELD(rct_config) FIELD(dests) FIELD(subaddr_account) @@ -528,13 +599,24 @@ private: std::unordered_set<crypto::public_key> signing_keys; rct::multisig_out msout; + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(sigs) FIELD(ignore) FIELD(used_L) FIELD(signing_keys) FIELD(msout) + FIELD(total_alpha_G) + FIELD(total_alpha_H) + FIELD(c_0) + FIELD(s) END_SERIALIZE() }; @@ -578,11 +660,15 @@ private: { std::vector<tx_construction_data> txes; std::pair<size_t, wallet2::transfer_container> transfers; + std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(txes) - FIELD(transfers) + if (version >= 1) + FIELD(new_transfers) + else + FIELD(transfers) END_SERIALIZE() }; @@ -760,45 +846,20 @@ private: * to other participants */ std::string make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold); + const std::vector<std::string> &kex_messages, + const std::uint32_t threshold); /*! - * \brief Creates a multisig wallet + * \brief Increment the multisig key exchange round * \return empty if done, non empty if we need to send another string * to other participants */ - std::string make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold); - std::string exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info); - /*! - * \brief Any but first round of keys exchange - */ std::string exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> pkeys, - std::vector<crypto::public_key> signers); - /*! - * \brief Finalizes creation of a multisig wallet - */ - bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info); + const std::vector<std::string> &kex_messages); /*! - * \brief Finalizes creation of a multisig wallet + * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called) + * \return string to send to other participants */ - bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers); - /*! - * Get a packaged multisig information string - */ - std::string get_multisig_info() const; - /*! - * Verifies and extracts keys from a packaged multisig information string - */ - static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); - /*! - * Verifies and extracts keys from a packaged multisig information string - */ - static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer); + std::string get_multisig_first_kex_msg() const; /*! * Export multisig info * This will generate and remember new k values @@ -956,11 +1017,11 @@ private: uint64_t unlocked_balance_all(bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, - std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags); void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, - std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config); + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); @@ -1080,9 +1141,7 @@ private: for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details &td = m_transfers[i]; - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); - m_pub_keys.emplace(o.key, i); + m_pub_keys.emplace(td.get_public_key(), i); } return; } @@ -1232,6 +1291,8 @@ private: void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } + void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; } @@ -1252,6 +1313,8 @@ private: void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); } uint64_t credits_target() const { return m_credits_target; } void credits_target(uint64_t threshold) { m_credits_target = threshold; } + bool is_multisig_enabled() const { return m_enable_multisig; } + void enable_multisig(bool enable) { m_enable_multisig = enable; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); @@ -1259,6 +1322,7 @@ private: void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); void check_tx_key_helper(const cryptonote::transaction &tx, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received) const; + bool is_out_to_acc(const cryptonote::account_public_address &address, const crypto::public_key& out_key, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const size_t output_index, const boost::optional<crypto::view_tag> &view_tag_opt, crypto::key_derivation &found_derivation) const; std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); std::string get_tx_proof(const cryptonote::transaction &tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) const; bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); @@ -1375,8 +1439,9 @@ private: bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> export_outputs(bool all = false) const; + std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const; std::string export_outputs_to_str(bool all = false) const; + size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; @@ -1414,8 +1479,9 @@ private: std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); - uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; + uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags, uint64_t base_fee, uint64_t fee_quantization_mask) const; uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); + uint64_t get_base_fee(uint32_t priority); uint64_t get_base_fee(); uint64_t get_fee_quantization_mask(); uint64_t get_min_ring_size(); @@ -1480,7 +1546,6 @@ private: void set_attribute(const std::string &key, const std::string &value); bool get_attribute(const std::string &key, std::string &value) const; - crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const; crypto::public_key get_multisig_signer_public_key() const; crypto::public_key get_multisig_signing_public_key(size_t idx) const; crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; @@ -1511,7 +1576,9 @@ private: const std::string get_ring_database() const { return m_ring_database; } bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs); + bool get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs); bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + bool set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative); bool unset_ring(const std::vector<crypto::key_image> &key_images); bool unset_ring(const crypto::hash &txid); bool find_and_save_rings(bool force = true); @@ -1618,9 +1685,9 @@ private: void set_unspent(size_t idx); bool is_spent(const transfer_details &td, bool strict = true) const; bool is_spent(size_t idx, bool strict = true) const; - void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct); - void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets); - bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache); + bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; void scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool); @@ -1628,7 +1695,7 @@ private: crypto::key_image get_multisig_composite_key_image(size_t n) const; rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; - rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const; + void get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce); void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n); bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); bool add_rings(const cryptonote::transaction_prefix &tx); @@ -1644,12 +1711,6 @@ private: bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); uint64_t get_segregation_fork_height() const; - void unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const; - bool unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const; void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const; @@ -1752,6 +1813,7 @@ private: uint64_t m_ignore_outputs_above; uint64_t m_ignore_outputs_below; bool m_track_uses; + bool m_show_wallet_name_when_locked; uint32_t m_inactivity_lock_timeout; BackgroundMiningSetupType m_setup_background_mining; bool m_persistent_rpc_client_id; @@ -1769,6 +1831,7 @@ private: crypto::secret_key m_rpc_client_secret_key; rpc_payment_state_t m_rpc_payment_state; uint64_t m_credits_target; + bool m_enable_multisig; // Aux transaction data from device serializable_unordered_map<crypto::hash, std::string> m_tx_device; @@ -1801,9 +1864,7 @@ private: crypto::secret_key m_original_view_secret_key; crypto::chacha_key m_cache_key; - boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; - boost::mutex m_decrypt_keys_lock; - unsigned int m_decrypt_keys_lockers; + std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh; bool m_unattended; bool m_devices_registered; @@ -1833,7 +1894,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) -BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0) +BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) namespace boost { @@ -2271,6 +2332,12 @@ namespace boost a & x.used_L; a & x.signing_keys; a & x.msout; + if (ver < 1) + return; + a & x.total_alpha_G; + a & x.total_alpha_H; + a & x.c_0; + a & x.s; } template <class Archive> diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 066e98e52..ce13fc573 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index 21e5f187c..350fce24e 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 011780f43..df594aa21 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_light_rpc.h b/src/wallet/wallet_light_rpc.h index c2a7dc021..743a147f6 100644 --- a/src/wallet/wallet_light_rpc.h +++ b/src/wallet/wallet_light_rpc.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h index 35714db03..93fa6996a 100644 --- a/src/wallet/wallet_rpc_helpers.h +++ b/src/wallet/wallet_rpc_helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // @@ -28,6 +28,7 @@ #pragma once +#include <limits> #include <type_traits> namespace diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index bf278f695..61eaa8070 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Monero Project +// Copyright (c) 2018-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 4655e24cd..7ec5fc7a1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -61,6 +61,17 @@ using namespace epee; #define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds +#define CHECK_MULTISIG_ENABLED() \ + do \ + { \ + if (m_wallet->multisig() && !m_wallet->is_multisig_enabled()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_DISABLED; \ + er.message = "This wallet is multisig, and multisig is disabled. Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member. You can enable it by running this once in monero-wallet-cli: set enable-multisig-experimental 1"; \ + return false; \ + } \ + } while(0) + namespace { const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; @@ -622,7 +633,7 @@ namespace tools res.total_unlocked_balance = 0; cryptonote::subaddress_index subaddr_index = {0,0}; const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); - if (!req.tag.empty() && account_tags.first.count(req.tag) == 0) + if (!req.tag.empty() && account_tags.first.count(req.tag) == 0 && !req.regexp) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = (boost::format(tr("Tag %s is unregistered.")) % req.tag).str(); @@ -630,7 +641,9 @@ namespace tools } for (; subaddr_index.major < m_wallet->get_num_subaddress_accounts(); ++subaddr_index.major) { - if (!req.tag.empty() && req.tag != account_tags.second[subaddr_index.major]) + bool no_match = !req.regexp ? (!req.tag.empty() && req.tag != account_tags.second[subaddr_index.major]) + : (!req.tag.empty() && !boost::regex_match(account_tags.second[subaddr_index.major], boost::regex(req.tag))); + if (no_match) continue; wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::subaddress_account_info info; info.account_index = subaddr_index.major; @@ -643,6 +656,12 @@ namespace tools res.total_balance += info.balance; res.total_unlocked_balance += info.unlocked_balance; } + if (res.subaddress_accounts.size() == 0 && req.regexp) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = (boost::format(tr("No matches for regex filter %s .")) % req.tag).str(); + return false; + } } catch (const std::exception& e) { @@ -1049,6 +1068,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { @@ -1101,6 +1122,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { @@ -1155,6 +1178,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) { @@ -1503,6 +1528,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + try { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(); @@ -1531,6 +1558,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra std::list<wallet_rpc::transfer_destination> destination; destination.push_back(wallet_rpc::transfer_destination()); @@ -1596,6 +1625,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra std::list<wallet_rpc::transfer_destination> destination; destination.push_back(wallet_rpc::transfer_destination()); @@ -1760,12 +1791,6 @@ namespace tools er.message = "Already integrated address"; return false; } - if (req.payment_id.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment ID shouldn't be left unspecified"; - return false; - } res.integrated_address = get_account_integrated_address_as_str(m_wallet->nettype(), info.address, payment_id); } res.payment_id = epee::string_tools::pod_to_hex(payment_id); @@ -3931,6 +3956,9 @@ namespace tools er.message = "This wallet is already multisig"; return false; } + if (req.enable_multisig_experimental) + m_wallet->enable_multisig(true); + CHECK_MULTISIG_ENABLED(); if (m_wallet->watch_only()) { er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; @@ -3938,7 +3966,7 @@ namespace tools return false; } - res.multisig_info = m_wallet->get_multisig_info(); + res.multisig_info = m_wallet->get_multisig_first_kex_msg(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -3957,6 +3985,7 @@ namespace tools er.message = "This wallet is already multisig"; return false; } + CHECK_MULTISIG_ENABLED(); if (m_wallet->watch_only()) { er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; @@ -4001,6 +4030,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata info; try @@ -4042,6 +4072,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); if (req.info.size() < threshold - 1) { @@ -4069,7 +4100,7 @@ namespace tools catch (const std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling import_multisig"; + er.message = std::string{"Error calling import_multisig: "} + e.what(); return false; } @@ -4094,53 +4125,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { - if (!m_wallet) return not_open(er); - if (m_restricted) - { - er.code = WALLET_RPC_ERROR_CODE_DENIED; - er.message = "Command unavailable in restricted mode."; - return false; - } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) - { - er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; - er.message = "This wallet is not multisig"; - return false; - } - if (ready) - { - er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; - er.message = "This wallet is multisig, and already finalized"; - return false; - } - - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) - { - er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; - er.message = "Needs multisig info from more participants"; - return false; - } - - try - { - if (!m_wallet->finalize_multisig(req.password, req.multisig_info)) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling finalize_multisig"; - return false; - } - } - catch (const std::exception &e) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = std::string("Error calling finalize_multisig: ") + e.what(); - return false; - } - res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - - return true; + CHECK_MULTISIG_ENABLED(); + return false; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx) @@ -4167,8 +4153,9 @@ namespace tools er.message = "This wallet is multisig, and already finalized"; return false; } + CHECK_MULTISIG_ENABLED(); - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) + if (req.multisig_info.size() + 1 < total) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Needs multisig info from more participants"; @@ -4178,7 +4165,8 @@ namespace tools try { res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info); - if (res.multisig_info.empty()) + m_wallet->multisig(&ready); + if (ready) { res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } @@ -4215,6 +4203,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) @@ -4284,6 +4273,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) @@ -4426,7 +4416,11 @@ namespace tools return false; } - if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) + boost::optional<epee::net_utils::http::login> daemon_login{}; + if (!req.username.empty() || !req.password.empty()) + daemon_login.emplace(req.username, req.password); + + if (!m_wallet->set_daemon(req.address, daemon_login, req.trusted, std::move(ssl_options))) { er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; er.message = std::string("Unable to set daemon"); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 7169c9136..3088fd9c2 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 248d31aa4..ecfc8e673 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -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 23 +#define WALLET_RPC_VERSION_MINOR 25 #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 @@ -243,10 +243,12 @@ namespace wallet_rpc { std::string tag; // all accounts if empty, otherwise those accounts with this tag bool strict_balances; + bool regexp; // allow regular expression filters if set to true BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tag) KV_SERIALIZE_OPT(strict_balances, false) + KV_SERIALIZE_OPT(regexp, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2414,7 +2416,10 @@ namespace wallet_rpc { struct request_t { + bool enable_multisig_experimental; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(enable_multisig_experimental, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2504,24 +2509,17 @@ namespace wallet_rpc struct COMMAND_RPC_FINALIZE_MULTISIG { + // NOP struct request_t { - std::string password; - std::vector<std::string> multisig_info; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(password) - KV_SERIALIZE(multisig_info) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; struct response_t { - std::string address; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2664,6 +2662,8 @@ namespace wallet_rpc struct request_t { std::string address; + std::string username; + std::string password; bool trusted; std::string ssl_support; // disabled, enabled, autodetect std::string ssl_private_key_path; @@ -2674,6 +2674,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) + KV_SERIALIZE(username) + KV_SERIALIZE(password) KV_SERIALIZE_OPT(trusted, false) KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect") KV_SERIALIZE(ssl_private_key_path) diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index b991029a9..734229380 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -78,3 +78,4 @@ #define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45 #define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46 #define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47 +#define WALLET_RPC_ERROR_CODE_DISABLED -48 |