aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt37
-rw-r--r--tests/benchmark.cpp437
-rw-r--r--tests/benchmark.h.in5
-rw-r--r--tests/functional_tests/check_missing_rpc_methods.py1
-rwxr-xr-xtests/functional_tests/proofs.py39
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/account.cpp34
-rw-r--r--tests/unit_tests/serialization.cpp40
-rw-r--r--tests/unit_tests/tx_proof.cpp130
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));
+}