diff options
44 files changed, 1210 insertions, 260 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd2e26484..69040d0af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: env: REMOVE_BUNDLED_BOOST : rm -rf /usr/local/share/boost BUILD_DEFAULT_LINUX: | - cmake -S . -B build -D ARCH="default" -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release && cmake --build build -j3 + cmake -S . -B build -D ARCH="default" -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release && cmake --build build -j3 APT_INSTALL_LINUX: 'sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler ccache' APT_SET_CONF: | echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom @@ -96,15 +96,15 @@ release: cmake-release release-test: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release $(topdir) && $(MAKE) && $(MAKE) test + cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) && $(MAKE) test release-all: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) release-static: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) coverage: mkdir -p $(builddir)/debug @@ -114,41 +114,41 @@ coverage: release-static-linux-armv6: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv6zk" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="linux-armv6" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv6zk" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="linux-armv6" $(topdir) && $(MAKE) release-static-linux-armv7: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="linux-armv7" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="linux-armv7" $(topdir) && $(MAKE) release-static-android-armv7: mkdir -p $(builddir)/release/translations cd $(builddir)/release/translations && cmake ../../../translations && $(MAKE) - cd $(builddir)/release && CC=arm-linux-androideabi-clang CXX=arm-linux-androideabi-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D BUILD_TAG="android-armv7" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARM_MODE=ON -D CMAKE_ANDROID_ARCH_ABI="armeabi-v7a" ../.. && $(MAKE) + cd $(builddir)/release && CC=arm-linux-androideabi-clang CXX=arm-linux-androideabi-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D ANDROID=true -D BUILD_TAG="android-armv7" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARM_MODE=ON -D CMAKE_ANDROID_ARCH_ABI="armeabi-v7a" ../.. && $(MAKE) release-static-android-armv8: mkdir -p $(builddir)/release/translations cd $(builddir)/release/translations && cmake ../../../translations && $(MAKE) - cd $(builddir)/release && CC=aarch64-linux-android-clang CXX=aarch64-linux-android-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D BUILD_TAG="android-armv8" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI="arm64-v8a" ../.. && $(MAKE) + cd $(builddir)/release && CC=aarch64-linux-android-clang CXX=aarch64-linux-android-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D ANDROID=true -D BUILD_TAG="android-armv8" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI="arm64-v8a" ../.. && $(MAKE) release-static-linux-armv8: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="linux-armv8" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="linux-armv8" $(topdir) && $(MAKE) release-static-linux-x86_64: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="linux-x64" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="linux-x64" $(topdir) && $(MAKE) release-static-freebsd-x86_64: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="freebsd-x64" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="freebsd-x64" $(topdir) && $(MAKE) release-static-mac-x86_64: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="mac-x64" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="mac-x64" $(topdir) && $(MAKE) release-static-linux-i686: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D BUILD_TAG="linux-x86" $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="linux-x86" $(topdir) && $(MAKE) release-static-win64: mkdir -p $(builddir)/release @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2022 The Monero Project. +Copyright (c) 2014-2022 The Monero Project. Portions Copyright (c) 2012-2013 The Cryptonote developers. ## Table of Contents @@ -33,7 +33,7 @@ Portions Copyright (c) 2012-2013 The Cryptonote developers. - Mail: [dev@getmonero.org](mailto:dev@getmonero.org) - GitHub: [https://github.com/monero-project/monero](https://github.com/monero-project/monero) - IRC: [#monero-dev on Libera](https://web.libera.chat/#monero-dev) -- It is HIGHLY recommended that you join the #monero-dev IRC channel if you are developing software that uses Monero. Due to the nature of this open source software project, joining this channel and idling is the best way to stay updated on best practices and new developments in the Monero ecosystem. All you need to do is join the IRC channel and idle to stay updated with the latest in Monero development. If you do not, you risk wasting resources on developing integrations that are not compatible with the Monero network. The Monero core team and community continuously make efforts to communicate updates, developments, and documentation via other platforms – but for the best information, you need to talk to other Monero developers, and they are on IRC. #monero-dev is about Monero development, not getting help about using Monero, or help about development of other software, including yours, unless it also pertains to Monero code itself. For these cases, checkout #monero. +- It is HIGHLY recommended that you join the #monero-dev IRC channel if you are developing software that uses Monero. Due to the nature of this open source software project, joining this channel and idling is the best way to stay updated on best practices and new developments in the Monero ecosystem. All you need to do is join the IRC channel and idle to stay updated with the latest in Monero development. If you do not, you risk wasting resources on developing integrations that are not compatible with the Monero network. The Monero core team and community continuously make efforts to communicate updates, developments, and documentation via other platforms – but for the best information, you need to talk to other Monero developers, and they are on IRC. #monero-dev is about Monero development, not getting help about using Monero, or help about development of other software, including yours, unless it also pertains to Monero code itself. For these cases, checkout #monero. ## Vulnerability response @@ -75,7 +75,7 @@ Monero is a private, secure, untraceable, decentralised digital currency. You ar **Untraceability:** By taking advantage of ring signatures, a special property of a certain type of cryptography, Monero is able to ensure that transactions are not only untraceable but have an optional measure of ambiguity that ensures that transactions cannot easily be tied back to an individual user or computer. -**Decentralization:** The utility of Monero depends on its decentralised peer-to-peer consensus network - anyone should be able to run the monero software, validate the integrity of the blockchain, and participate in all aspects of the monero network using consumer-grade commodity hardware. Decentralization of the monero network is maintained by software development that minimizes the costs of running the monero software and inhibits the proliferation of specialized, non-commodity hardware. +**Decentralization:** The utility of Monero depends on its decentralised peer-to-peer consensus network - anyone should be able to run the monero software, validate the integrity of the blockchain, and participate in all aspects of the monero network using consumer-grade commodity hardware. Decentralization of the monero network is maintained by software development that minimizes the costs of running the monero software and inhibits the proliferation of specialized, non-commodity hardware. ## About this project @@ -117,7 +117,7 @@ Monero uses a fixed-schedule software upgrade (hard fork) mechanism to implement Dates are provided in the format YYYY-MM-DD. -| Software upgrade block height | Date | Fork version | Minimum Monero version | Recommended Monero version | Details | +| Software upgrade block height | Date | Fork version | Minimum Monero version | Recommended Monero version | Details | | ------------------------------ | -----------| ----------------- | ---------------------- | -------------------------- | ---------------------------------------------------------------------------------- | | 1009827 | 2016-03-22 | v2 | v0.9.4 | v0.9.4 | Allow only >= ringsize 3, blocktime = 120 seconds, fee-free blocksize 60 kb | | 1141317 | 2016-09-21 | v3 | v0.9.4 | v0.10.0 | Splits coinbase into denominations | @@ -196,30 +196,44 @@ then: Install all dependencies at once on Debian/Ubuntu: -``` sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev ccache doxygen graphviz ``` +``` +sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev ccache doxygen graphviz +``` Install all dependencies at once on openSUSE: -``` sudo zypper ref && sudo zypper in cppzmq-devel ldns-devel libboost_chrono-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_program_options-devel libboost_regex-devel libboost_serialization-devel libboost_system-devel libboost_thread-devel libexpat-devel libminiupnpc-devel libsodium-devel libunwind-devel unbound-devel cmake doxygen ccache fdupes gcc-c++ libevent-devel libopenssl-devel pkgconf-pkg-config readline-devel xz-devel libqt5-qttools-devel patterns-devel-C-C++-devel_C_C++``` +``` +sudo zypper ref && sudo zypper in cppzmq-devel ldns-devel libboost_chrono-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_program_options-devel libboost_regex-devel libboost_serialization-devel libboost_system-devel libboost_thread-devel libexpat-devel libminiupnpc-devel libsodium-devel libunwind-devel unbound-devel cmake doxygen ccache fdupes gcc-c++ libevent-devel libopenssl-devel pkgconf-pkg-config readline-devel xz-devel libqt5-qttools-devel patterns-devel-C-C++-devel_C_C++ +``` Install all dependencies at once on macOS with the provided Brewfile: -``` brew update && brew bundle --file=contrib/brew/Brewfile ``` + +``` +brew update && brew bundle --file=contrib/brew/Brewfile +``` FreeBSD 12.1 one-liner required to build dependencies: -```pkg install git gmake cmake pkgconf boost-libs libzmq4 libsodium unbound``` + +``` +pkg install git gmake cmake pkgconf boost-libs libzmq4 libsodium unbound +``` ### Cloning the repository Clone recursively to pull-in needed submodule(s): -`$ git clone --recursive https://github.com/monero-project/monero` +``` +git clone --recursive https://github.com/monero-project/monero +``` If you already have a repo cloned, initialize and update: -`$ cd monero && git submodule init && git submodule update` +``` +cd monero && git submodule init && git submodule update +``` *Note*: If there are submodule differences between branches, you may need -to use ```git submodule sync && git submodule update``` after changing branches +to use `git submodule sync && git submodule update` after changing branches to build successfully. ### Build instructions @@ -383,15 +397,15 @@ application. * Download and install the [MSYS2 installer](https://www.msys2.org), either the 64-bit or the 32-bit package, depending on your system. * Open the MSYS shell via the `MSYS2 Shell` shortcut -* Update packages using pacman: +* Update packages using pacman: ```bash pacman -Syu ``` -* Exit the MSYS shell using Alt+F4 +* Exit the MSYS shell using Alt+F4 * Edit the properties for the `MSYS2 Shell` shortcut changing "msys2_shell.bat" to "msys2_shell.cmd -mingw64" for 64-bit builds or "msys2_shell.cmd -mingw32" for 32-bit builds -* Restart MSYS shell via modified shortcut and update packages again using pacman: +* Restart MSYS shell via modified shortcut and update packages again using pacman: ```bash pacman -Syu @@ -471,7 +485,7 @@ application. The project can be built from scratch by following instructions for Linux above(but use `gmake` instead of `make`). If you are running Monero in a jail, you need to add `sysvsem="new"` to your jail configuration, otherwise lmdb will throw the error message: `Failed to open lmdb environment: Function not implemented`. -Monero is also available as a port or package as 'monero-cli`. +Monero is also available as a port or package as `monero-cli`. ### On OpenBSD: diff --git a/external/randomx b/external/randomx -Subproject 9efc398c196ef1c50d8e6f5e1f2c5ac02f1f6ce +Subproject ae8e98b681766f31d49ac562dd6974906156e07 diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 3522c4a1b..e2ac9df0b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1045,8 +1045,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")); @@ -1074,7 +1075,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) diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 1f46164d7..77a36069a 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -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 596090329..d8cd6c6a0 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -99,6 +99,10 @@ 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); @@ -107,7 +111,7 @@ namespace crypto { static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 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(); @@ -151,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); @@ -297,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; } @@ -312,6 +326,9 @@ 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; @@ -325,3 +342,4 @@ 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/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 4ae29281a..127958796 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -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_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index d7149b5c9..493c9d91d 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -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; diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index d8f9bb176..432617a4f 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -155,12 +155,13 @@ 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) @@ -852,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; @@ -905,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); @@ -914,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)) + { + 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 }; + } + // 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)) + { + hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + } } return boost::none; } @@ -973,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; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index ce3ce7492..8f5459ca7 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -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); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 968efc52d..f02eeee71 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -184,6 +184,7 @@ #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 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index ed47f9433..5b7b4353d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1341,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__); @@ -1369,6 +1370,8 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, 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; } //------------------------------------------------------------------ @@ -3044,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; } } } @@ -3166,6 +3171,13 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v15, require view tags on outputs + if (!check_output_types(tx, hf_version)) + { + tvc.m_invalid_output = true; + return false; + } + return true; } //------------------------------------------------------------------ @@ -3983,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; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 2d2b1cbcf..a78f5d673 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1177,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; @@ -1189,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; } //----------------------------------------------------------------------------------------------- @@ -1295,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 2e5248c5d..0b36730b6 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1012,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 76192be5d..1d2024a05 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -149,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); } @@ -198,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, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -406,17 +411,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; @@ -546,7 +550,9 @@ namespace cryptonote } 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; } @@ -607,7 +613,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, rct::multisig_out *msout, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -627,7 +633,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, msout, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -643,7 +650,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}, NULL, false); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index be6a6d946..f4ffb98ff 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -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 }, rct::multisig_out *msout = NULL, 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 }, rct::multisig_out *msout = NULL, 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 diff --git a/src/device/device.hpp b/src/device/device.hpp index ba47115e7..eca91006f 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -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_default.cpp b/src/device/device_default.cpp index e7b452d40..d70ece229 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -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 60d2ba203..7d3543652 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -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_ledger.cpp b/src/device/device_ledger.cpp index 378d9f533..51e65dfa5 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -524,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) { @@ -1527,7 +1528,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 @@ -1541,6 +1543,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) { @@ -1568,7 +1572,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 06521a56f..074bfaa8d 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -273,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_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 31a242604..a400e82c7 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -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/serialization/crypto.h b/src/serialization/crypto.h index 7be04c910..c2a4846ee 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -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/json_object.cpp b/src/serialization/json_object.cpp index e98e327e5..8de5860f6 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -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; diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index e7caa8377..968707165 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -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); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a98a827ca..f30225e1d 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -9761,7 +9761,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); } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index c824e2d95..7cd8656e1 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1743,6 +1743,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str 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_quantization_mask()); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f51906e6e..f0b29b9a2 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -781,7 +781,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, bool bulletproof_plus) +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; @@ -821,6 +821,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; */ @@ -837,17 +840,17 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra 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, bool bulletproof_plus) +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, bulletproof_plus); + 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, bool bulletproof_plus) +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, bulletproof_plus); + 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 = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) @@ -878,6 +881,11 @@ uint8_t get_clsag_fork() return HF_VERSION_CLSAG; } +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) @@ -1765,13 +1773,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 @@ -1856,17 +1865,20 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons } } + 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"); } @@ -1993,8 +2005,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; @@ -2064,10 +2074,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); @@ -2087,32 +2095,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) @@ -2793,25 +2775,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)) @@ -2825,18 +2816,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; @@ -6613,7 +6637,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin 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, m_multisig ? &msout : NULL, 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, @@ -6698,16 +6722,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"); } @@ -7132,7 +7157,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto 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); + bool shuffle_outs = false; + 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, shuffle_outs, sd.use_view_tags); 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), @@ -7232,16 +7258,16 @@ 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, bool bulletproof_plus, uint64_t base_fee, 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, bulletproof_plus); + 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, bulletproof_plus); + 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); } } @@ -8465,7 +8491,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; @@ -8474,7 +8500,7 @@ 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) @@ -8565,7 +8591,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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) + 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 @@ -8638,7 +8665,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); @@ -8676,7 +8703,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent 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, {}, m_multisig ? &msout : NULL, 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); @@ -8714,6 +8741,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; @@ -8725,7 +8753,7 @@ 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) + 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 @@ -8910,7 +8938,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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); + 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, 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); @@ -8955,7 +8983,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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); + bool shuffle_outs = false; + 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, shuffle_outs, 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); @@ -9002,6 +9031,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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; @@ -9701,6 +9731,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp rct::RangeProofPaddedBulletproof, bulletproof_plus ? 4 : 3 }; + 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(); @@ -9734,7 +9765,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 = (base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus)); + 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) @@ -9752,8 +9783,8 @@ 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, bulletproof_plus); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + 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 = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -9850,7 +9881,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, 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()) { @@ -9963,7 +9994,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, bulletproof_plus) < 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) << @@ -9981,7 +10012,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } 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) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + 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)); @@ -10019,7 +10050,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, bulletproof_plus); + 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); } @@ -10030,7 +10061,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, bulletproof_plus, base_fee, 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) { @@ -10079,10 +10110,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp 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); + 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); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); @@ -10104,10 +10135,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp 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); + 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); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -10177,7 +10208,8 @@ skip_tx: 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, @@ -10189,7 +10221,8 @@ skip_tx: 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; @@ -10292,9 +10325,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below 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 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); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + 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 = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -10406,6 +10440,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton rct::RangeProofPaddedBulletproof, bulletproof_plus ? 4 : 3 }; + 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(); @@ -10432,7 +10467,7 @@ 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, bulletproof_plus); + 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 @@ -10463,7 +10498,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, bulletproof_plus); + 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) { @@ -10471,7 +10506,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, bulletproof_plus, base_fee, 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) @@ -10481,10 +10516,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton 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); + 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); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; @@ -10518,10 +10553,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton } 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); + 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); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -10557,10 +10592,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton 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); + 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); + 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; @@ -10629,7 +10664,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; @@ -11099,13 +11134,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"); @@ -11304,24 +11338,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) @@ -11415,6 +11437,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 @@ -11951,8 +12009,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); @@ -11967,7 +12025,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; @@ -11976,7 +12034,7 @@ 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); + crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey); THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, "The address doesn't seem to have received the fund"); @@ -12430,11 +12488,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; @@ -12551,11 +12605,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)) { @@ -13011,9 +13061,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)) @@ -14239,8 +14287,9 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i 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, bulletproof_plus); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + 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 59f1ca94a..660e6a14b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -347,7 +347,12 @@ 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(!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) @@ -529,10 +534,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) @@ -540,7 +556,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) @@ -967,10 +1002,10 @@ private: 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); + 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); + 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); @@ -1090,9 +1125,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; } @@ -1271,6 +1304,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); @@ -1427,7 +1461,7 @@ 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, bool bulletproof_plus, uint64_t base_fee, 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(); diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index 28f1de7d0..711f67b41 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -671,3 +671,127 @@ bool gen_block_low_coinbase::generate(std::vector<test_event_entry>& events) con return true; } + +bool gen_block_miner_tx_out_has_no_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + bool use_view_tags = false; + + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + + CHECK_AND_ASSERT_MES(!cryptonote::get_output_view_tag(miner_tx.vout[0]), false, "output should not have a view tag"); + + crypto::public_key output_public_key; + crypto::view_tag view_tag; + cryptonote::get_output_public_key(miner_tx.vout[0], output_public_key); + + // explicitly call the setter to ensure it does not set a view tag on the miner tx output + cryptonote::set_tx_out(miner_tx.vout[0].amount, output_public_key, use_view_tags, view_tag, miner_tx.vout[0]); + CHECK_AND_ASSERT_MES(!cryptonote::get_output_view_tag(miner_tx.vout[0]), false, "output should still not have a view tag"); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + +bool gen_block_miner_tx_out_has_no_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + bool use_view_tags = false; + + BLOCK_VALIDATION_INIT_GENERATE(); + + keypair txkey; + MAKE_MINER_TX_AND_KEY_AT_HF_MANUALLY(miner_tx, blk_0, HF_VERSION_VIEW_TAGS+1, &txkey); + + crypto::public_key output_public_key; + crypto::view_tag view_tag; + cryptonote::get_output_public_key(miner_tx.vout[0], output_public_key); + + // remove the view tag that is currently set on the miner tx output at this point + cryptonote::set_tx_out(miner_tx.vout[0].amount, output_public_key, use_view_tags, view_tag, miner_tx.vout[0]); + CHECK_AND_ASSERT_MES(!cryptonote::get_output_view_tag(miner_tx.vout[0]), false, "output should not have a view tag"); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_miner_tx, + HF_VERSION_VIEW_TAGS+1, HF_VERSION_VIEW_TAGS+1, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_out_has_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + bool use_view_tags = true; + + BLOCK_VALIDATION_INIT_GENERATE(); + + keypair txkey; + MAKE_MINER_TX_AND_KEY_MANUALLY(miner_tx, blk_0, &txkey); + + // derive the view tag for the miner tx output + crypto::key_derivation derivation; + crypto::public_key output_public_key; + crypto::view_tag view_tag; + crypto::generate_key_derivation(miner_account.get_keys().m_account_address.m_view_public_key, txkey.sec, derivation); + crypto::derive_public_key(derivation, 0, miner_account.get_keys().m_account_address.m_spend_public_key, output_public_key); + crypto::derive_view_tag(derivation, 0, view_tag); + + // set the view tag on the miner tx output + cryptonote::set_tx_out(miner_tx.vout[0].amount, output_public_key, use_view_tags, view_tag, miner_tx.vout[0]); + boost::optional<crypto::view_tag> actual_vt = cryptonote::get_output_view_tag(miner_tx.vout[0]); + CHECK_AND_ASSERT_MES(actual_vt && *actual_vt == view_tag, false, "unexpected output view tag"); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_out_has_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + bool use_view_tags = true; + + BLOCK_VALIDATION_INIT_GENERATE(); + + keypair txkey; + MAKE_MINER_TX_AND_KEY_AT_HF_MANUALLY(miner_tx, blk_0, HF_VERSION_VIEW_TAGS, &txkey); + + CHECK_AND_ASSERT_MES(cryptonote::get_output_view_tag(miner_tx.vout[0]), false, "output should have a view tag"); + + // derive the view tag for the miner tx output + crypto::key_derivation derivation; + crypto::public_key output_public_key; + crypto::view_tag view_tag; + crypto::generate_key_derivation(miner_account.get_keys().m_account_address.m_view_public_key, txkey.sec, derivation); + crypto::derive_public_key(derivation, 0, miner_account.get_keys().m_account_address.m_spend_public_key, output_public_key); + crypto::derive_view_tag(derivation, 0, view_tag); + + boost::optional<crypto::view_tag> actual_vt = cryptonote::get_output_view_tag(miner_tx.vout[0]); + CHECK_AND_ASSERT_MES(actual_vt && *actual_vt == view_tag, false, "unexpected output view tag"); + + // set the view tag on the miner tx output + cryptonote::set_tx_out(miner_tx.vout[0].amount, output_public_key, use_view_tags, view_tag, miner_tx.vout[0]); + boost::optional<crypto::view_tag> actual_vt_after_setting = cryptonote::get_output_view_tag(miner_tx.vout[0]); + CHECK_AND_ASSERT_MES(actual_vt_after_setting && *actual_vt_after_setting == view_tag, false, "unexpected output view tag after setting"); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_miner_tx, + HF_VERSION_VIEW_TAGS, HF_VERSION_VIEW_TAGS, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} diff --git a/tests/core_tests/block_validation.h b/tests/core_tests/block_validation.h index 2535039fb..3a157aaa9 100644 --- a/tests/core_tests/block_validation.h +++ b/tests/core_tests/block_validation.h @@ -230,3 +230,37 @@ struct get_test_options<gen_block_low_coinbase> { hard_forks, 0 }; }; + +struct gen_block_miner_tx_out_has_no_view_tag_before_hf_view_tags : public gen_block_accepted_base<2> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_out_has_no_view_tag_from_hf_view_tags : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> +struct get_test_options<gen_block_miner_tx_out_has_no_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(HF_VERSION_VIEW_TAGS+1, 1), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +struct gen_block_miner_tx_out_has_view_tag_before_hf_view_tags : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_out_has_view_tag_from_hf_view_tags : public gen_block_accepted_base<2> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> +struct get_test_options<gen_block_miner_tx_out_has_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(HF_VERSION_VIEW_TAGS, 1), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 689b6801b..61195c7b0 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -462,7 +462,9 @@ bool init_output_indices(map_output_idx_t& outs, std::map<uint64_t, std::vector< size_t tx_global_idx = outs[out.amount].size() - 1; outs[out.amount][tx_global_idx].idx = tx_global_idx; // Is out to me? - if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), j)) { + crypto::public_key output_public_key; + cryptonote::get_output_public_key(out, output_public_key); + if (is_out_to_acc(from.get_keys(), output_public_key, get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), j)) { outs_mine[out.amount].push_back(tx_global_idx); } } @@ -972,7 +974,7 @@ std::vector<cryptonote::tx_destination_entry> build_dsts(std::initializer_list<d bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, - keypair* p_txkey/* = 0*/) + uint8_t hf_version/* = 1*/, keypair* p_txkey/* = 0*/) { keypair txkey; txkey = keypair::generate(hw::get_device("default")); @@ -987,7 +989,7 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE uint64_t block_reward; - if (!get_block_reward(0, 0, already_generated_coins, block_reward, 1)) + if (!get_block_reward(0, 0, already_generated_coins, block_reward, hf_version)) { LOG_PRINT_L0("Block is too big"); return false; @@ -999,12 +1001,20 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key); + bool use_view_tags = hf_version >= HF_VERSION_VIEW_TAGS; + crypto::view_tag view_tag; + if (use_view_tags) + crypto::derive_view_tag(derivation, 0, view_tag); + tx_out out; - out.amount = block_reward; - out.target = txout_to_key(out_eph_public_key); + cryptonote::set_tx_out(block_reward, out_eph_public_key, use_view_tags, view_tag, out); + tx.vout.push_back(out); - tx.version = 1; + if (hf_version >= HF_VERSION_DYNAMIC_FEE) + tx.version = 2; + else + tx.version = 1; tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; return true; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index c1e592b86..5c8c9f79f 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -423,7 +423,8 @@ uint64_t sum_amount(const std::vector<cryptonote::tx_source_entry>& sources); bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, const cryptonote::account_public_address& miner_address, cryptonote::transaction& tx, - uint64_t fee, cryptonote::keypair* p_txkey = nullptr); + uint64_t fee, uint8_t hf_version = 1, + cryptonote::keypair* p_txkey = nullptr); bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, @@ -967,12 +968,14 @@ inline bool do_replay_file(const std::string& filename) std::list<cryptonote::transaction> SET_NAME; \ MAKE_TX_MIX_LIST_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD); -#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) \ +#define MAKE_MINER_TX_AND_KEY_AT_HF_MANUALLY(TX, BLK, HF_VERSION, KEY) \ transaction TX; \ if (!construct_miner_tx_manually(get_block_height(BLK) + 1, generator.get_already_generated_coins(BLK), \ - miner_account.get_keys().m_account_address, TX, 0, KEY)) \ + miner_account.get_keys().m_account_address, TX, 0, HF_VERSION, KEY)) \ return false; +#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) MAKE_MINER_TX_AND_KEY_AT_HF_MANUALLY(TX, BLK, 1, KEY) + #define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0) #define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT) VEC_EVENTS.push_back(event_visitor_settings(SETT)); diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 2e2d170ff..b89dff445 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -133,6 +133,10 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_block_miner_tx_out_is_big); GENERATE_AND_PLAY(gen_block_miner_tx_has_no_out); GENERATE_AND_PLAY(gen_block_miner_tx_has_out_to_alice); + GENERATE_AND_PLAY(gen_block_miner_tx_out_has_no_view_tag_before_hf_view_tags); + GENERATE_AND_PLAY(gen_block_miner_tx_out_has_no_view_tag_from_hf_view_tags); + GENERATE_AND_PLAY(gen_block_miner_tx_out_has_view_tag_before_hf_view_tags); + GENERATE_AND_PLAY(gen_block_miner_tx_out_has_view_tag_from_hf_view_tags); GENERATE_AND_PLAY(gen_block_has_invalid_tx); GENERATE_AND_PLAY(gen_block_is_too_big); GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10 @@ -219,6 +223,15 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_rct_tx_pre_rct_increase_vin_and_fee); GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_has_no_view_tag_before_hf_view_tags); + // TODO: base test needs to be restructured to handle pre rct outputs after HF v12 + // GENERATE_AND_PLAY(gen_rct_tx_pre_rct_has_no_view_tag_from_hf_view_tags); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_has_view_tag_before_hf_view_tags); + // GENERATE_AND_PLAY(gen_rct_tx_pre_rct_has_view_tag_from_hf_view_tags); + GENERATE_AND_PLAY(gen_rct_tx_rct_has_no_view_tag_before_hf_view_tags); + GENERATE_AND_PLAY(gen_rct_tx_rct_has_no_view_tag_from_hf_view_tags); + GENERATE_AND_PLAY(gen_rct_tx_rct_has_view_tag_before_hf_view_tags); + GENERATE_AND_PLAY(gen_rct_tx_rct_has_view_tag_from_hf_view_tags); GENERATE_AND_PLAY(gen_rct_tx_uses_output_too_early); GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2); diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 3a26de4c2..0926483fe 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -41,7 +41,7 @@ using namespace cryptonote; // Tests bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry>& events, - const int *out_idx, int mixin, uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid, + const int *out_idx, int mixin, uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool use_view_tags, bool valid, const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, const std::function<void(transaction &tx)> &post_tx) const { @@ -98,7 +98,9 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry const size_t index_in_tx = 5; src.amount = 30000000000000; for (int m = 0; m < 4; ++m) { - src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount); + crypto::public_key output_public_key; + cryptonote::get_output_public_key(blocks[m].miner_tx.vout[index_in_tx], output_public_key); + src.push_output(m, output_public_key, src.amount); } src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx); src.real_output = n; @@ -139,10 +141,13 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default")); } + uint64_t fee = 0; + get_tx_fee(rct_txes[n], fee); + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes[n], blk_last, miner_account, - test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs | test_generator::bf_tx_fees, 4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long - crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 4), + crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 4, fee), false, "Failed to generate block"); events.push_back(blk_txes[n]); blk_last = blk_txes[n]; @@ -224,7 +229,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config); + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, NULL, use_view_tags); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) @@ -244,7 +249,8 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev const std::function<void(transaction &tx)> &post_tx) const { const rct::RCTConfig rct_config { rct::RangeProofBorromean, 0 }; - return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, 4, rct_config, valid, pre_tx, post_tx); + bool use_view_tags = false; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, 4, rct_config, use_view_tags, valid, pre_tx, post_tx); } bool gen_rct_tx_valid_from_pre_rct::generate(std::vector<test_event_entry>& events) const @@ -517,11 +523,99 @@ bool gen_rct_tx_rct_altered_extra::generate(std::vector<test_event_entry>& event NULL, [&failed](transaction &tx) {std::string extra_nonce; crypto::hash pid = crypto::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) failed = true; }) && !failed; } +bool gen_rct_tx_pre_rct_has_no_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + bool use_view_tags = false; + bool valid = true; + return generate_with_full(events, out_idx, mixin, amount_paid, 0, 0, {}, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_pre_rct_has_no_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 10; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 3 }; + bool use_view_tags = false; + bool valid = false; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, HF_VERSION_VIEW_TAGS, rct_config, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_pre_rct_has_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + bool use_view_tags = true; + bool valid = false; + return generate_with_full(events, out_idx, mixin, amount_paid, 0, 0, {}, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_pre_rct_has_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 10; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 3 }; + bool use_view_tags = true; + bool valid = true; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, HF_VERSION_VIEW_TAGS, rct_config, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_rct_has_no_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofBorromean, 0 }; + bool use_view_tags = false; + bool valid = true; + return generate_with_full(events, out_idx, mixin, amount_paid, 0, 0, rct_config, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_rct_has_no_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 10; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 3 }; + bool use_view_tags = false; + bool valid = false; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, HF_VERSION_VIEW_TAGS+1, rct_config, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_rct_has_view_tag_before_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofBorromean, 0 }; + bool use_view_tags = true; + bool valid = false; + return generate_with_full(events, out_idx, mixin, amount_paid, 0, 0, rct_config, use_view_tags, valid, NULL, NULL); +} + +bool gen_rct_tx_rct_has_view_tag_from_hf_view_tags::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 10; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 3 }; + bool use_view_tags = true; + bool valid = true; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, HF_VERSION_VIEW_TAGS, rct_config, use_view_tags, valid, NULL, NULL); +} + bool gen_rct_tx_uses_output_too_early::generate(std::vector<test_event_entry>& events) const { const int mixin = 10; const int out_idx[] = {1, -1}; const uint64_t amount_paid = 10000; const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 2 }; - return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE-3, HF_VERSION_ENFORCE_MIN_AGE, rct_config, false, NULL, NULL); + bool use_view_tags = false; + bool valid = false; + return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE-3, HF_VERSION_ENFORCE_MIN_AGE, rct_config, use_view_tags, valid, NULL, NULL); } diff --git a/tests/core_tests/rct.h b/tests/core_tests/rct.h index dee074e4c..c20e8a580 100644 --- a/tests/core_tests/rct.h +++ b/tests/core_tests/rct.h @@ -70,7 +70,7 @@ struct gen_rct_tx_validation_base : public test_chain_unit_base } bool generate_with_full(std::vector<test_event_entry>& events, const int *out_idx, int mixin, - uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid, + uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool use_view_tags, bool valid, const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, const std::function<void(cryptonote::transaction &tx)> &post_tx) const; bool generate_with(std::vector<test_event_entry>& events, const int *out_idx, int mixin, @@ -266,6 +266,74 @@ struct gen_rct_tx_rct_altered_extra : public gen_rct_tx_validation_base }; template<> struct get_test_options<gen_rct_tx_rct_altered_extra>: public get_test_options<gen_rct_tx_validation_base> {}; +struct gen_rct_tx_pre_rct_has_no_view_tag_before_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_has_no_view_tag_before_hf_view_tags>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_has_no_view_tag_from_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_has_no_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[5] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(HF_VERSION_VIEW_TAGS, 69), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +struct gen_rct_tx_pre_rct_has_view_tag_before_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_has_view_tag_before_hf_view_tags>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_has_view_tag_from_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_has_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[5] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(HF_VERSION_VIEW_TAGS, 69), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +struct gen_rct_tx_rct_has_no_view_tag_before_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_has_no_view_tag_before_hf_view_tags>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_has_no_view_tag_from_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_has_no_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[5] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(HF_VERSION_VIEW_TAGS+1, 69), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +struct gen_rct_tx_rct_has_view_tag_before_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_has_view_tag_before_hf_view_tags>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_has_view_tag_from_hf_view_tags : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_has_view_tag_from_hf_view_tags> { + const std::pair<uint8_t, uint64_t> hard_forks[5] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(HF_VERSION_VIEW_TAGS, 69), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + struct gen_rct_tx_uses_output_too_early : public gen_rct_tx_validation_base { bool generate(std::vector<test_event_entry>& events) const; diff --git a/tests/crypto/main.cpp b/tests/crypto/main.cpp index 59a2a7d77..045ffc08d 100644 --- a/tests/crypto/main.cpp +++ b/tests/crypto/main.cpp @@ -260,7 +260,6 @@ int main(int argc, char *argv[]) { goto error; } } else if (cmd == "check_ge_p3_identity") { - cerr << "Testing: " << cmd << endl; public_key point; bool expected_bad, expected_good, result_badfunc, result_goodfunc; get(input, point, expected_bad, expected_good); @@ -269,6 +268,15 @@ int main(int argc, char *argv[]) { if (expected_bad != result_badfunc || expected_good != result_goodfunc) { goto error; } + } else if (cmd == "derive_view_tag") { + key_derivation derivation; + size_t output_index; + view_tag expected, actual; + get(input, derivation, output_index, expected); + derive_view_tag(derivation, output_index, actual); + if (expected != actual) { + goto error; + } } else { throw ios_base::failure("Unknown function: " + cmd); } diff --git a/tests/crypto/tests.txt b/tests/crypto/tests.txt index 3a7fe5453..d387aa09d 100644 --- a/tests/crypto/tests.txt +++ b/tests/crypto/tests.txt @@ -5473,3 +5473,59 @@ check_ge_p3_identity 046e1450f147f3ade34d149973913cc75d4e7b9669eb1ed61da0f1d4a0b check_ge_p3_identity ca8a2f621cfc7aa3efcd7ddf55dce5352e757b38aca0869b050c0a27824e5c5e true true check_ge_p3_identity 64a247eef6087d86e1e9fa048a3c181fdb1728431f29ba738634bdc38f02a859 true true check_ge_p3_identity cff0c7170a41395b0658ee42b76545c45360736b973ab2f31f6f227b9415df67 true true +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 0 76 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 1 d6 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 2 87 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 3 1b +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 12 d6 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 13 e9 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 14 12 +derive_view_tag 0fc47054f355ced4d67de73bfa12e4c78ff19089548fffa7d07a674741860f97 15 26 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 0 70 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 1 81 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 2 a0 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 3 ec +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 12 22 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 13 0a +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 14 87 +derive_view_tag a36ba7b4d31349ad278a6df8f77adb76748b59f4929348e67dd92adb9fa174dc 15 76 +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 0 93 +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 1 67 +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 2 9d +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 3 2d +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 12 63 +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 13 cf +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 14 ef +derive_view_tag 7498d5bf0b69e08653f6d420a17f866dd2bd490ab43074f46065cb501fe7e2d8 15 10 +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 0 90 +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 1 5a +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 2 de +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 3 21 +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 12 57 +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 13 52 +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 14 6f +derive_view_tag fe7770c4b076e95ddb8026affcfab39d31c7c4a2266e0e25e343bc4badc907d0 15 eb +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 0 c6 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 1 60 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 2 f0 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 3 71 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 12 0e +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 13 42 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 14 b2 +derive_view_tag ea9337d0ddf480abdc4fc56a0cb223702729cb230ae7b9de50243ad25ce90e8d 15 61 +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 0 4c +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 1 9b +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 2 64 +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 3 ff +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 12 e3 +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 13 24 +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 14 ea +derive_view_tag 25d538315bcb81aff9574189ea65f418aeb0392f5cbbc84cd8a33c7ade31ef0a 15 3b +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 0 74 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 1 77 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 2 a9 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 3 44 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 12 75 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 13 05 +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 14 ca +derive_view_tag 8edfabada2b24ef4d8d915826c9ff0245910e4b835b59c2cf8ed8fc991b2e1e8 15 00 diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt index 03b124981..c079afe3a 100644 --- a/tests/performance_tests/CMakeLists.txt +++ b/tests/performance_tests/CMakeLists.txt @@ -43,6 +43,7 @@ set(performance_tests_headers generate_keypair.h signature.h is_out_to_acc.h + out_can_be_to_acc.h subaddress_expand.h range_proof.h bulletproof.h diff --git a/tests/performance_tests/derive_view_tag.h b/tests/performance_tests/derive_view_tag.h new file mode 100644 index 000000000..ee4efb16d --- /dev/null +++ b/tests/performance_tests/derive_view_tag.h @@ -0,0 +1,62 @@ +// Copyright (c) 2014-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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_basic.h" + +#include "single_tx_test_base.h" + +class test_derive_view_tag : public single_tx_test_base +{ +public: + static const size_t loop_count = 10000; + + bool init() + { + if (!single_tx_test_base::init()) + return false; + + crypto::generate_key_derivation(m_tx_pub_key, m_bob.get_keys().m_view_secret_key, m_key_derivation); + + return true; + } + + bool test() + { + crypto::view_tag view_tag; + crypto::derive_view_tag(m_key_derivation, 0, view_tag); + return true; + } + +private: + crypto::key_derivation m_key_derivation; +}; diff --git a/tests/performance_tests/is_out_to_acc.h b/tests/performance_tests/is_out_to_acc.h index d1204ee68..75d4a5c46 100644 --- a/tests/performance_tests/is_out_to_acc.h +++ b/tests/performance_tests/is_out_to_acc.h @@ -43,8 +43,9 @@ public: bool test() { - const cryptonote::txout_to_key& tx_out = boost::get<cryptonote::txout_to_key>(m_tx.vout[0].target); - return cryptonote::is_out_to_acc(m_bob.get_keys(), tx_out, m_tx_pub_key, m_additional_tx_pub_keys, 0); + crypto::public_key output_public_key; + cryptonote::get_output_public_key(m_tx.vout[0], output_public_key); + return cryptonote::is_out_to_acc(m_bob.get_keys(), output_public_key, m_tx_pub_key, m_additional_tx_pub_keys, 0); } }; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index 8bd1008cb..71c736977 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -42,6 +42,7 @@ #include "cn_slow_hash.h" #include "derive_public_key.h" #include "derive_secret_key.h" +#include "derive_view_tag.h" #include "ge_frombytes_vartime.h" #include "ge_tobytes.h" #include "generate_key_derivation.h" @@ -50,6 +51,7 @@ #include "generate_keypair.h" #include "signature.h" #include "is_out_to_acc.h" +#include "out_can_be_to_acc.h" #include "subaddress_expand.h" #include "sc_reduce32.h" #include "sc_check.h" @@ -194,6 +196,9 @@ int main(int argc, char** argv) TEST_PERFORMANCE0(filter, p, test_is_out_to_acc); TEST_PERFORMANCE0(filter, p, test_is_out_to_acc_precomp); + TEST_PERFORMANCE2(filter, p, test_out_can_be_to_acc, false, true); // no view tag, owned + TEST_PERFORMANCE2(filter, p, test_out_can_be_to_acc, true, false); // use view tag, not owned + TEST_PERFORMANCE2(filter, p, test_out_can_be_to_acc, true, true); // use view tag, owned TEST_PERFORMANCE0(filter, p, test_generate_key_image_helper); TEST_PERFORMANCE0(filter, p, test_generate_key_derivation); TEST_PERFORMANCE0(filter, p, test_generate_key_image); @@ -206,6 +211,7 @@ int main(int argc, char** argv) TEST_PERFORMANCE0(filter, p, test_sc_check); TEST_PERFORMANCE1(filter, p, test_signature, false); TEST_PERFORMANCE1(filter, p, test_signature, true); + TEST_PERFORMANCE0(filter, p, test_derive_view_tag); TEST_PERFORMANCE2(filter, p, test_wallet2_expand_subaddresses, 50, 200); diff --git a/tests/performance_tests/out_can_be_to_acc.h b/tests/performance_tests/out_can_be_to_acc.h new file mode 100644 index 000000000..86e236b7b --- /dev/null +++ b/tests/performance_tests/out_can_be_to_acc.h @@ -0,0 +1,103 @@ +// Copyright (c) 2014-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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_basic.h" + +#include "single_tx_test_base.h" + +using namespace crypto; + +// use_view_tags: whether to enable view tag checking +// is_owned: whether the output is owned by us +template<bool use_view_tags, bool is_owned> +class test_out_can_be_to_acc : public single_tx_test_base +{ + public: + static const size_t loop_count = 1000; + + bool init() + { + if (!single_tx_test_base::init()) + return false; + + crypto::key_derivation key_derivation; + crypto::view_tag vt; + + m_output_index = 0; + m_view_secret_key = m_bob.get_keys().m_view_secret_key; + m_spend_public_key = m_bob.get_keys().m_account_address.m_spend_public_key; + + cryptonote::get_output_public_key(m_tx.vout[m_output_index], m_output_public_key); + + if (use_view_tags) + { + crypto::generate_key_derivation(m_tx_pub_key, m_view_secret_key, key_derivation); + crypto::derive_view_tag(key_derivation, m_output_index, vt); + m_view_tag_opt = vt; + } + else + m_view_tag_opt = boost::optional<crypto::view_tag>(); + + return true; + } + + bool test() + { + // include key derivation to demonstrate performance improvement when using view tags + crypto::key_derivation key_derivation; + crypto::generate_key_derivation(m_tx_pub_key, m_view_secret_key, key_derivation); + + // if using view tags, this ensures we computed the view tag properly + if (!cryptonote::out_can_be_to_acc(m_view_tag_opt, key_derivation, m_output_index)) + return false; + + // if user owns output, this tests the output public key matches the derived + if (is_owned) + { + crypto::public_key output_public_key; + crypto::derive_public_key(key_derivation, m_output_index, m_spend_public_key, output_public_key); + + if (m_output_public_key != output_public_key) + return false; + } + + return true; + } + + private: + size_t m_output_index; + crypto::secret_key m_view_secret_key; + crypto::public_key m_spend_public_key; + crypto::public_key m_output_public_key; + boost::optional<crypto::view_tag> m_view_tag_opt; +}; |