diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 37 | ||||
-rw-r--r-- | tests/benchmark.cpp | 437 | ||||
-rw-r--r-- | tests/benchmark.h.in | 5 | ||||
-rw-r--r-- | tests/functional_tests/check_missing_rpc_methods.py | 1 | ||||
-rwxr-xr-x | tests/functional_tests/proofs.py | 39 | ||||
-rw-r--r-- | tests/unit_tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/unit_tests/account.cpp | 34 | ||||
-rw-r--r-- | tests/unit_tests/serialization.cpp | 40 | ||||
-rw-r--r-- | tests/unit_tests/tx_proof.cpp | 130 |
9 files changed, 719 insertions, 5 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed5a3b9e3..c601b93ed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,8 @@ # # Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +set(MONERO_WALLET_CRYPTO_BENCH "auto" CACHE STRING "Select wallet crypto libraries for benchmarking") + # The docs say this only affects grouping in IDEs set(folder "tests") set(TEST_DATA_DIR "${CMAKE_CURRENT_LIST_DIR}/data") @@ -118,6 +120,41 @@ add_test( NAME hash-target COMMAND hash-target-tests) +# +# Configure wallet crypto benchmark +# +if (${MONERO_WALLET_CRYPTO_BENCH} STREQUAL "auto") + set(MONERO_WALLET_CRYPTO_BENCH "cn") + monero_crypto_autodetect(AVAILABLE BEST) + if (DEFINED AVAILABLE) + list(APPEND MONERO_WALLET_CRYPTO_BENCH ${AVAILABLE}) + endif () + message("Wallet crypto bench is using ${MONERO_WALLET_CRYPTO_BENCH}") +endif () + +list(REMOVE_DUPLICATES MONERO_WALLET_CRYPTO_BENCH) +list(REMOVE_ITEM MONERO_WALLET_CRYPTO_BENCH "cn") # always used for comparison +set(MONERO_WALLET_CRYPTO_BENCH_NAMES "(cn)") +foreach(BENCH IN LISTS MONERO_WALLET_CRYPTO_BENCH) + monero_crypto_valid(${BENCH} VALID) + if (NOT VALID) + message(FATAL_ERROR "Invalid MONERO_WALLET_CRYPTO_BENCH option ${BENCH}") + endif () + + monero_crypto_get_target(${BENCH} BENCH_LIBRARY) + list(APPEND BENCH_OBJECTS $<TARGET_OBJECTS:${BENCH_LIBRARY}>) + + monero_crypto_get_namespace(${BENCH} BENCH_NAMESPACE) + set(MONERO_WALLET_CRYPTO_BENCH_NAMES "${MONERO_WALLET_CRYPTO_BENCH_NAMES}(${BENCH_NAMESPACE})") +endforeach () + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/benchmark.h.in" "${MONERO_GENERATED_HEADERS_DIR}/tests/benchmark.h") +add_executable(monero-wallet-crypto-bench benchmark.cpp ${BENCH_OBJECTS}) +target_link_libraries(monero-wallet-crypto-bench cncrypto) + +add_test(NAME wallet-crypto-bench COMMAND monero-wallet-crypto-bench) + + set(enabled_tests core_tests difficulty diff --git a/tests/benchmark.cpp b/tests/benchmark.cpp new file mode 100644 index 000000000..0461f4c11 --- /dev/null +++ b/tests/benchmark.cpp @@ -0,0 +1,437 @@ +// Copyright (c) 2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "tests/benchmark.h" + +#include <boost/fusion/adapted/std_tuple.hpp> +#include <boost/fusion/algorithm/iteration/fold.hpp> +#include <boost/preprocessor/seq/enum.hpp> +#include <boost/preprocessor/seq/for_each.hpp> +#include <boost/preprocessor/seq/seq.hpp> +#include <boost/preprocessor/seq.hpp> +#include <boost/preprocessor/stringize.hpp> +#include <boost/spirit/include/karma_char.hpp> +#include <boost/spirit/include/karma_format.hpp> +#include <boost/spirit/include/karma_repeat.hpp> +#include <boost/spirit/include/karma_right_alignment.hpp> +#include <boost/spirit/include/karma_sequence.hpp> +#include <boost/spirit/include/karma_string.hpp> +#include <boost/spirit/include/karma_uint.hpp> +#include <boost/spirit/include/qi_char.hpp> +#include <boost/spirit/include/qi_list.hpp> +#include <boost/spirit/include/qi_parse.hpp> +#include <boost/spirit/include/qi_uint.hpp> +#include <chrono> +#include <cstring> +#include <functional> +#include <iostream> +#include <stdexcept> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "monero/crypto/amd64-64-24k.h" +#include "monero/crypto/amd64-51-30k.h" + +#define CHECK(...) \ + if(!( __VA_ARGS__ )) \ + throw std::runtime_error{ \ + "TEST FAILED (line " \ + BOOST_PP_STRINGIZE( __LINE__ ) \ + "): " \ + BOOST_PP_STRINGIZE( __VA_ARGS__ ) \ + } + +//! Define function that forwards arguments to `crypto::func`. +#define FORWARD_FUNCTION(func) \ + template<typename... T> \ + static bool func (T&&... args) \ + { \ + return ::crypto:: func (std::forward<T>(args)...); \ + } + +#define CRYPTO_FUNCTION(library, func) \ + BOOST_PP_CAT(BOOST_PP_CAT(monero_crypto_, library), func) + +#define CRYPTO_BENCHMARK(r, _, library) \ + struct library \ + { \ + static constexpr const char* name() noexcept { return BOOST_PP_STRINGIZE(library); } \ + static bool generate_key_derivation(const ::crypto::public_key &tx_pub, const ::crypto::secret_key &view_sec, ::crypto::key_derivation &out) \ + { \ + return CRYPTO_FUNCTION(library, _generate_key_derivation) (out.data, tx_pub.data, view_sec.data) == 0; \ + } \ + static bool derive_subaddress_public_key(const ::crypto::public_key &spend_pub, const ::crypto::key_derivation &d, std::size_t index, ::crypto::public_key &out) \ + { \ + ::crypto::ec_scalar scalar; \ + ::crypto::derivation_to_scalar(d, index, scalar); \ + return CRYPTO_FUNCTION(library, _generate_subaddress_public_key) (out.data, spend_pub.data, scalar.data) == 0; \ + } \ + }; + + +namespace +{ + //! Default number of iterations for benchmark timing. + constexpr const unsigned default_iterations = 1000; + + //! \return Byte compare two objects of `T`. + template<typename T> + bool compare(const T& lhs, const T& rhs) noexcept + { + static_assert(!epee::has_padding<T>(), "type might have padding"); + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(T)) == 0; + } + + //! Benchmark default monero crypto library - a re-arranged ref10 implementation. + struct cn + { + static constexpr const char* name() noexcept { return "cn"; } + FORWARD_FUNCTION( generate_key_derivation ); + FORWARD_FUNCTION( derive_subaddress_public_key ); + }; + + // Define functions for every library except for `cn` which is the head library. + BOOST_PP_SEQ_FOR_EACH(CRYPTO_BENCHMARK, _, BOOST_PP_SEQ_TAIL(BENCHMARK_LIBRARIES)); + + // All enabled benchmark libraries + using enabled_libraries = std::tuple<BOOST_PP_SEQ_ENUM(BENCHMARK_LIBRARIES)>; + + + //! Callable that runs a benchmark against all enabled libraries + template<typename R> + struct run_benchmark + { + using result = R; + + template<typename B> + result operator()(result out, const B benchmark) const + { + using inner_result = typename B::result; + out.push_back({boost::fusion::fold(enabled_libraries{}, inner_result{}, benchmark), benchmark.name()}); + std::sort(out.back().first.begin(), out.back().first.end()); + return out; + } + }; + + //! Run 0+ benchmarks against all enabled libraries + template<typename R, typename... B> + R run_benchmarks(B&&... benchmarks) + { + auto out = boost::fusion::fold(std::make_tuple(std::forward<B>(benchmarks)...), R{}, run_benchmark<R>{}); + std::sort(out.begin(), out.end()); + return out; + } + + //! Run a suite of benchmarks - allows for comparison against a subset of benchmarks + template<typename S> + std::pair<typename S::result, std::string> run_suite(const S& suite) + { + return {suite(), suite.name()}; + } + + //! Arguments given to every crypto library being benchmarked. + struct bench_args + { + explicit bench_args(unsigned iterations) + : iterations(iterations), one(), two() + { + crypto::generate_keys(one.pub, one.sec, one.sec, false); + crypto::generate_keys(two.pub, two.sec, two.sec, false); + } + + const unsigned iterations; + cryptonote::keypair one; + cryptonote::keypair two; + }; + + /*! Tests the ECDH step used for monero txes where the tx-pub is always + de-compressed into a table every time. */ + struct tx_pub_standard + { + using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>; + static constexpr const char* name() noexcept { return "standard"; } + + const bench_args args; + + template<typename L> + result operator()(result out, const L library) const + { + crypto::key_derivation us; + crypto::key_derivation them; + CHECK(crypto::generate_key_derivation(args.one.pub, args.two.sec, them)); + CHECK(library.generate_key_derivation(args.one.pub, args.two.sec, us)); + CHECK(compare(us, them)); + + unsigned i = 0; + for (unsigned j = 0; j < 100; ++j) + i += library.generate_key_derivation(args.one.pub, args.two.sec, us); + CHECK(i == 100); + + i = 0; + const auto start = std::chrono::steady_clock::now(); + for (unsigned j = 0; j < args.iterations; ++j) + i += library.generate_key_derivation(args.one.pub, args.two.sec, us); + const auto end = std::chrono::steady_clock::now(); + CHECK(i == args.iterations); + CHECK(compare(us, them)); + + out.push_back({end - start, library.name()}); + return out; + } + }; + + //! Tests various possible optimizations for tx ECDH-step. + struct tx_pub_suite + { + using result = std::vector<std::pair<tx_pub_standard::result, std::string>>; + static constexpr const char* name() noexcept { return "generate_key_derivation step"; } + + const bench_args args; + + result operator()() const + { + return run_benchmarks<result>(tx_pub_standard{args}); + } + }; + + /*! Tests the shared-secret to output-key step used for monero txes where + the users spend-public is always de-compressed. */ + struct output_pub_standard + { + using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>; + static constexpr const char* name() noexcept { return "standard"; } + + const bench_args args; + + template<typename L> + result operator()(result out, const L library) const + { + crypto::key_derivation derived; + crypto::public_key us; + crypto::public_key them; + CHECK(crypto::generate_key_derivation(args.one.pub, args.two.sec, derived)); + CHECK(library.derive_subaddress_public_key(args.two.pub, derived, 0, us)); + CHECK(crypto::derive_subaddress_public_key(args.two.pub, derived, 0, them)); + CHECK(compare(us, them)); + + unsigned i = 0; + for (unsigned j = 0; j < 100; ++j) + i += library.derive_subaddress_public_key(args.two.pub, derived, j, us); + CHECK(i == 100); + + i = 0; + const auto start = std::chrono::steady_clock::now(); + for (unsigned j = 0; j < args.iterations; ++j) + i += library.derive_subaddress_public_key(args.two.pub, derived, j, us); + const auto end = std::chrono::steady_clock::now(); + CHECK(i == args.iterations); + + out.push_back({end - start, library.name()}); + return out; + } + }; + + //! Tests various possible optimizations for shared-secret to output-key step. + struct output_pub_suite + { + using result = std::vector<std::pair<output_pub_standard::result, std::string>>; + static constexpr const char* name() noexcept { return "derive_subaddress_public_key step"; } + + const bench_args args; + + result operator()() const + { + return run_benchmarks<result>(output_pub_standard{args}); + } + }; + + struct tx_bench_args + { + const bench_args main; + unsigned outputs; + }; + + /*! Simulates "standard" tx scanning where a tx-pubkey is de-compressed into + a table and user spend-public is de-compressed, every time. */ + struct tx_standard + { + using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>; + static constexpr const char* name() noexcept { return "standard"; } + + const tx_bench_args args; + + template<typename L> + result operator()(result out, const L library) const + { + crypto::key_derivation derived_us; + crypto::key_derivation derived_them; + crypto::public_key us; + crypto::public_key them; + CHECK(library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us)); + CHECK(crypto::generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_them)); + CHECK(library.derive_subaddress_public_key(args.main.two.pub, derived_us, 0, us)); + CHECK(crypto::derive_subaddress_public_key(args.main.two.pub, derived_them, 0, them)); + CHECK(compare(us, them)); + + unsigned i = 0; + for (unsigned j = 0; j < 100; ++j) + { + i += library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us); + i += library.derive_subaddress_public_key(args.main.two.pub, derived_us, j, us); + } + CHECK(i == 200); + + i = 0; + const auto start = std::chrono::steady_clock::now(); + for (unsigned j = 0; j < args.main.iterations; ++j) + { + i += library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us); + for (unsigned k = 0; k < args.outputs; ++k) + i += library.derive_subaddress_public_key(args.main.two.pub, derived_us, k, us); + } + const auto end = std::chrono::steady_clock::now(); + CHECK(i == args.main.iterations + args.main.iterations * args.outputs); + + out.push_back({end - start, library.name()}); + return out; + } + }; + + //! Tests various possible optimizations for tx scanning. + struct tx_suite + { + using result = std::vector<std::pair<output_pub_standard::result, std::string>>; + std::string name() const { return "Transactions with " + std::to_string(args.outputs) + " outputs"; } + + const tx_bench_args args; + + result operator()() const + { + return run_benchmarks<result>(tx_standard{args}); + + } + }; + + std::chrono::steady_clock::duration print(const tx_pub_standard::result& leaf, std::ostream& out, unsigned depth) + { + namespace karma = boost::spirit::karma; + const std::size_t align = leaf.empty() ? + 0 : std::to_string(leaf.back().first.count()).size(); + const auto best = leaf.empty() ? + std::chrono::steady_clock::duration::max() : leaf.front().first; + for (auto const& entry : leaf) + { + out << karma::format(karma::repeat(depth ? depth - 1 : 0)["| "]) << '|'; + out << karma::format((karma::right_align(std::min(20u - depth, 20u), '-')["> " << karma::string]), entry.second); + out << " => " << karma::format((karma::right_align(align)[karma::uint_]), entry.first.count()); + out << " ns (+"; + out << (double((entry.first - best).count()) / best.count()) * 100 << "%)" << std::endl; + } + out << karma::format(karma::repeat(depth ? depth - 1 : 0)["| "]) << std::endl; + return best; + } + + template<typename T> + std::chrono::steady_clock::duration + print(const std::vector<std::pair<T, std::string>>& node, std::ostream& out, unsigned depth) + { + auto best = std::chrono::steady_clock::duration::max(); + for (auto const& entry : node) + { + std::stringstream buffer{}; + auto last = print(entry.first, buffer, depth + 1); + if (last != std::chrono::steady_clock::duration::max()) + { + namespace karma = boost::spirit::karma; + best = std::min(best, last); + out << karma::format(karma::repeat(depth)["|-"]); + out << "+ " << entry.second << ' '; + out << last.count() << " ns (+"; + out << (double((last - best).count()) / best.count()) * 100 << "%)" << std::endl; + out << buffer.str(); + } + } + return best; + } +} // anonymous namespace + +int main(int argc, char** argv) +{ + using results = std::vector<std::pair<tx_pub_suite::result, std::string>>; + try + { + unsigned iterations = default_iterations; + std::vector<unsigned> nums{}; + if (2 <= argc) iterations = std::stoul(argv[1]); + if (3 <= argc) + { + namespace qi = boost::spirit::qi; + if (!qi::parse(argv[2], argv[2] + strlen(argv[2]), (qi::uint_ % ','), nums)) + throw std::runtime_error{"bad tx outputs string"}; + } + else + { + nums = {2, 4}; + } + std::sort(nums.begin(), nums.end()); + nums.erase(std::unique(nums.begin(), nums.end()), nums.end()); + + std::cout << "Running benchmark using " << iterations << " iterations" << std::endl; + + const bench_args args{iterations}; + + results val{}; + + std::cout << "Transaction Component Benchmarks" << std::endl; + std::cout << "--------------------------------" << std::endl; + val.push_back(run_suite(tx_pub_suite{args})); + val.push_back(run_suite(output_pub_suite{args})); + std::sort(val.begin(), val.end()); + print(val, std::cout, 0); + + val.clear(); + std::cout << "Transaction Benchmarks" << std::endl; + std::cout << "----------------------" << std::endl; + for (const unsigned num : nums) + val.push_back(run_suite(tx_suite{{args, num}})); + std::sort(val.begin(), val.end()); + print(val, std::cout, 0); + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} + diff --git a/tests/benchmark.h.in b/tests/benchmark.h.in new file mode 100644 index 000000000..b13ea30b7 --- /dev/null +++ b/tests/benchmark.h.in @@ -0,0 +1,5 @@ +#pragma once + +// A Boost PP sequence +#define BENCHMARK_LIBRARIES @MONERO_WALLET_CRYPTO_BENCH_NAMES@ + diff --git a/tests/functional_tests/check_missing_rpc_methods.py b/tests/functional_tests/check_missing_rpc_methods.py index 6fadebf9b..0eedd6d0f 100644 --- a/tests/functional_tests/check_missing_rpc_methods.py +++ b/tests/functional_tests/check_missing_rpc_methods.py @@ -46,5 +46,6 @@ for module in modules: name = name[1:] if not hasattr(module['object'], name): print('Error: %s API method %s does not have a matching function' % (module['name'], name)) + error = True sys.exit(1 if error else 0) diff --git a/tests/functional_tests/proofs.py b/tests/functional_tests/proofs.py index 5f23f7ea4..e58d29f94 100755 --- a/tests/functional_tests/proofs.py +++ b/tests/functional_tests/proofs.py @@ -130,13 +130,13 @@ class ProofsTest(): sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo'); - assert res.signature.startswith('InProof'); + assert res.signature.startswith('InProofV2'); signature0i = res.signature res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar'); - assert res.signature.startswith('OutProof'); + assert res.signature.startswith('OutProofV2'); signature0o = res.signature res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz'); - assert res.signature.startswith('InProof'); + assert res.signature.startswith('InProofV2'); signature1 = res.signature res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i); @@ -219,6 +219,23 @@ class ProofsTest(): except: ok = True assert ok or not res.good + + # Test bad cross-version verification + ok = False + try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i.replace('ProofV2','ProofV1')); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o.replace('ProofV2','ProofV1')); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1.replace('ProofV2','ProofV1')); + except: ok = True + assert ok or not res.good + def check_spend_proof(self, txid): daemon = Daemon() @@ -270,7 +287,7 @@ class ProofsTest(): balance1 = res.balance res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo') - assert res.signature.startswith('ReserveProof') + assert res.signature.startswith('ReserveProofV2') signature = res.signature for i in range(2): res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) @@ -287,9 +304,15 @@ class ProofsTest(): except: ok = True assert ok or not res.good + # Test bad cross-version verification + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1')) + except: ok = True + assert ok or not res.good + amount = int(balance0 / 10) res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo') - assert res.signature.startswith('ReserveProof') + assert res.signature.startswith('ReserveProofV2') signature = res.signature for i in range(2): res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) @@ -306,6 +329,12 @@ class ProofsTest(): except: ok = True assert ok or not res.good + # Test bad cross-version verification + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1')) + except: ok = True + assert ok or not res.good + ok = False try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo') except: ok = True diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 4f1b0c22a..a5984b2c9 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -83,6 +83,7 @@ set(unit_tests_sources test_peerlist.cpp test_protocol_pack.cpp threadpool.cpp + tx_proof.cpp hardfork.cpp unbound.cpp uri.cpp diff --git a/tests/unit_tests/account.cpp b/tests/unit_tests/account.cpp index 2ab2f893a..68bf4dce7 100644 --- a/tests/unit_tests/account.cpp +++ b/tests/unit_tests/account.cpp @@ -29,14 +29,30 @@ #include "gtest/gtest.h" #include "cryptonote_basic/account.h" +#include "ringct/rctOps.h" +// Tests in-memory encryption of account secret keys TEST(account, encrypt_keys) { + // Generate account keys and random multisig keys cryptonote::keypair recovery_key = cryptonote::keypair::generate(hw::get_device("default")); cryptonote::account_base account; crypto::secret_key key = account.generate(recovery_key.sec); + + const size_t n_multisig = 4; + std::vector<crypto::secret_key> multisig_keys; + multisig_keys.reserve(n_multisig); + multisig_keys.resize(0); + for (size_t i = 0; i < n_multisig; ++i) + { + multisig_keys.push_back(rct::rct2sk(rct::skGen())); + } + ASSERT_TRUE(account.make_multisig(account.get_keys().m_view_secret_key, account.get_keys().m_spend_secret_key, account.get_keys().m_account_address.m_spend_public_key, multisig_keys)); + const cryptonote::account_keys keys = account.get_keys(); + ASSERT_EQ(keys.m_multisig_keys.size(),n_multisig); + // Encrypt and decrypt keys ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); @@ -50,22 +66,40 @@ TEST(account, encrypt_keys) ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.decrypt_viewkey(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.encrypt_viewkey(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); + + account.decrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); + + account.encrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.decrypt_keys(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_EQ(account.get_keys().m_multisig_keys, keys.m_multisig_keys); } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index ee205e666..b460559ff 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -616,6 +616,46 @@ TEST(Serialization, serializes_ringct_types) ASSERT_EQ(bp0, bp1); } +TEST(Serialization, key_encryption_transition) +{ + const cryptonote::network_type nettype = cryptonote::TESTNET; + tools::wallet2 w(nettype); + const boost::filesystem::path wallet_file = unit_test::data_dir / "wallet_9svHk1"; + const boost::filesystem::path key_file = unit_test::data_dir / "wallet_9svHk1.keys"; + const boost::filesystem::path temp_wallet_file = unit_test::data_dir / "wallet_9svHk1_temp"; + const boost::filesystem::path temp_key_file = unit_test::data_dir / "wallet_9svHk1_temp.keys"; + string password = "test"; + bool r = false; + + // Copy the original files for this test + boost::filesystem::copy(wallet_file,temp_wallet_file); + boost::filesystem::copy(key_file,temp_key_file); + + try + { + // Key transition + w.load(temp_wallet_file.string(), password); // legacy decryption method + ASSERT_TRUE(w.get_load_info().is_legacy_key_encryption); + const crypto::secret_key view_secret_key = w.get_account().get_keys().m_view_secret_key; + + w.rewrite(temp_wallet_file.string(), password); // transition to new key format + + w.load(temp_wallet_file.string(), password); // new decryption method + ASSERT_FALSE(w.get_load_info().is_legacy_key_encryption); + ASSERT_EQ(w.get_account().get_keys().m_view_secret_key,view_secret_key); + + r = true; + } + catch (const exception& e) + {} + + // Remove the temporary files + boost::filesystem::remove(temp_wallet_file); + boost::filesystem::remove(temp_key_file); + + ASSERT_TRUE(r); +} + TEST(Serialization, portability_wallet) { const cryptonote::network_type nettype = cryptonote::TESTNET; diff --git a/tests/unit_tests/tx_proof.cpp b/tests/unit_tests/tx_proof.cpp new file mode 100644 index 000000000..c5d06bc68 --- /dev/null +++ b/tests/unit_tests/tx_proof.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include "crypto/crypto.h" +extern "C" { +#include "crypto/crypto-ops.h" +} +#include "crypto/hash.h" +#include <boost/algorithm/string.hpp> + +static inline unsigned char *operator &(crypto::ec_point &point) { + return &reinterpret_cast<unsigned char &>(point); + } + +static inline unsigned char *operator &(crypto::ec_scalar &scalar) { + return &reinterpret_cast<unsigned char &>(scalar); + } + +TEST(tx_proof, prove_verify_v2) +{ + crypto::secret_key r; + crypto::random32_unbiased(&r); + + // A = aG + // B = bG + crypto::secret_key a,b; + crypto::public_key A,B; + crypto::generate_keys(A, a, a, false); + crypto::generate_keys(B, b, b, false); + + // R_B = rB + crypto::public_key R_B; + ge_p3 B_p3; + ge_frombytes_vartime(&B_p3,&B); + ge_p2 R_B_p2; + ge_scalarmult(&R_B_p2, &unwrap(r), &B_p3); + ge_tobytes(&R_B, &R_B_p2); + + // R_G = rG + crypto::public_key R_G; + ge_frombytes_vartime(&B_p3,&B); + ge_p3 R_G_p3; + ge_scalarmult_base(&R_G_p3, &unwrap(r)); + ge_p3_tobytes(&R_G, &R_G_p3); + + // D = rA + crypto::public_key D; + ge_p3 A_p3; + ge_frombytes_vartime(&A_p3,&A); + ge_p2 D_p2; + ge_scalarmult(&D_p2, &unwrap(r), &A_p3); + ge_tobytes(&D, &D_p2); + + crypto::signature sig; + + // Message data + crypto::hash prefix_hash; + char data[] = "hash input"; + crypto::cn_fast_hash(data,sizeof(data)-1,prefix_hash); + + // Generate/verify valid v1 proof with standard address + crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1)); + + // Generate/verify valid v1 proof with subaddress + crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1)); + + // Generate/verify valid v2 proof with standard address + crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2)); + + // Generate/verify valid v2 proof with subaddress + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2)); + + // Try to verify valid v2 proofs as v1 proof (bad) + crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1)); + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1)); + + // Randomly-distributed test points + crypto::secret_key evil_a, evil_b, evil_d, evil_r; + crypto::public_key evil_A, evil_B, evil_D, evil_R; + crypto::generate_keys(evil_A, evil_a, evil_a, false); + crypto::generate_keys(evil_B, evil_b, evil_b, false); + crypto::generate_keys(evil_D, evil_d, evil_d, false); + crypto::generate_keys(evil_R, evil_r, evil_r, false); + + // Selectively choose bad point in v2 proof (bad) + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, evil_R, A, B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, evil_A, B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, evil_B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, evil_D, sig, 2)); + + // Try to verify valid v1 proofs as v2 proof (bad) + crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2)); + crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2)); +} |