From a11ec4ac1d335677842b447208a9b45d531beeb0 Mon Sep 17 00:00:00 2001
From: Lee Clagett <code@leeclagett.com>
Date: Fri, 15 Nov 2019 14:08:28 +0000
Subject: [PATCH] Support for supercop ASM in wallet, and benchmark for
 supercop

---
 .gitmodules                        |   4 +
 CMakeLists.txt                     |   7 +-
 contrib/depends/toolchain.cmake.in |   5 +-
 external/supercop                  |   1 +
 src/crypto/CMakeLists.txt          |   3 +
 src/crypto/wallet/CMakeLists.txt   |  62 ++++
 src/crypto/wallet/crypto.h         |  56 ++++
 src/crypto/wallet/empty.h.in       |  31 ++
 src/device/CMakeLists.txt          |   1 +
 src/device/device_default.cpp      |   5 +-
 tests/CMakeLists.txt               |  37 +++
 tests/benchmark.cpp                | 437 +++++++++++++++++++++++++++++
 tests/benchmark.h.in               |   5 +
 13 files changed, 649 insertions(+), 5 deletions(-)
 create mode 160000 external/supercop
 create mode 100644 src/crypto/wallet/CMakeLists.txt
 create mode 100644 src/crypto/wallet/crypto.h
 create mode 100644 src/crypto/wallet/empty.h.in
 create mode 100644 tests/benchmark.cpp
 create mode 100644 tests/benchmark.h.in

diff --git a/.gitmodules b/.gitmodules
index f8e7c305b..9dacf534f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -15,3 +15,7 @@
 [submodule "external/randomx"]
 	path = external/randomx
 	url = https://github.com/tevador/RandomX
+[submodule "external/supercop"]
+	path = external/supercop
+	url = https://github.com/monero-project/supercop
+	branch = monero
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f63c07a35..51e497260 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -210,6 +210,7 @@ if(NOT MANUAL_SUBMODULES)
     check_submodule(external/rapidjson)
     check_submodule(external/trezor-common)
     check_submodule(external/randomx)
+    check_submodule(external/supercop)
   endif()
 endif()
 
@@ -311,7 +312,7 @@ endif()
 # elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*")
 #   set(BSDI TRUE)
 
-include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external)
+include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include)
 
 if(APPLE)
   include_directories(SYSTEM /usr/include/malloc)
@@ -456,6 +457,9 @@ add_definition_if_function_found(strptime HAVE_STRPTIME)
 
 add_definitions(-DAUTO_INITIALIZE_EASYLOGGINGPP)
 
+set(MONERO_GENERATED_HEADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated_include")
+include_directories(${MONERO_GENERATED_HEADERS_DIR})
+
 # Generate header for embedded translations
 # Generate header for embedded translations, use target toolchain if depends, otherwise use the
 # lrelease and lupdate binaries from the host
@@ -987,6 +991,7 @@ if(SODIUM_LIBRARY)
   set(ZMQ_LIB "${ZMQ_LIB};${SODIUM_LIBRARY}")
 endif()
 
+include(external/supercop/functions.cmake) # place after setting flags and before src directory inclusion
 add_subdirectory(contrib)
 add_subdirectory(src)
 
diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in
index 2634423ab..422d9dede 100644
--- a/contrib/depends/toolchain.cmake.in
+++ b/contrib/depends/toolchain.cmake.in
@@ -1,5 +1,6 @@
 # Set the system name to one of Android, Darwin, FreeBSD, Linux, or Windows
 SET(CMAKE_SYSTEM_NAME @depends@)
+SET(CMAKE_SYSTEM_PROCESSOR @arch@)
 SET(CMAKE_BUILD_TYPE @release_type@)
 
 OPTION(STATIC "Link libraries statically" ON)
@@ -63,14 +64,14 @@ set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # Find programs on host
 set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # Find libs in target
 set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # Find includes in target
 
-set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR} CACHE STRING "" FORCE)
-
 # specify the cross compiler to be used. Darwin uses clang provided by the SDK.
 if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
   SET(CMAKE_C_COMPILER @prefix@/native/bin/clang)
   SET(CMAKE_C_COMPILER_TARGET x86_64-apple-darwin11)
   SET(CMAKE_CXX_COMPILER @prefix@/native/bin/clang++ -stdlib=libc++)
   SET(CMAKE_CXX_COMPILER_TARGET x86_64-apple-darwin11)
+  SET(CMAKE_ASM_COMPILER_TARGET x86_64-apple-darwin11)
+  SET(CMAKE_ASM-ATT_COMPILER_TARGET x86_64-apple-darwin11)
   SET(_CMAKE_TOOLCHAIN_PREFIX x86_64-apple-darwin11-)
   SET(APPLE True)
   SET(BUILD_TAG "mac-x64")
diff --git a/external/supercop b/external/supercop
new file mode 160000
index 000000000..7d8b68782
--- /dev/null
+++ b/external/supercop
@@ -0,0 +1 @@
+Subproject commit 7d8b6878260061da56ade6d23dc833288659d0a3
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index 318e6dc57..3b33fe90a 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -116,3 +116,6 @@ endif()
 
 # cheat because cmake and ccache hate each other
 set_property(SOURCE CryptonightR_template.S PROPERTY LANGUAGE C)
+
+# Must be done last, because it references libraries in this directory
+add_subdirectory(wallet)
diff --git a/src/crypto/wallet/CMakeLists.txt b/src/crypto/wallet/CMakeLists.txt
new file mode 100644
index 000000000..4ed986dce
--- /dev/null
+++ b/src/crypto/wallet/CMakeLists.txt
@@ -0,0 +1,62 @@
+# 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.
+
+#
+# Possibly user defined values.
+#
+set(MONERO_WALLET_CRYPTO_LIBRARY "auto" CACHE STRING "Select a wallet crypto library")
+
+#
+# If the user specified "auto", detect best library defaulting to internal.
+#
+if (${MONERO_WALLET_CRYPTO_LIBRARY} STREQUAL "auto")
+   monero_crypto_autodetect(AVAILABLE BEST)
+  if (DEFINED BEST)
+    message("Wallet crypto is using ${BEST} backend")
+    set(MONERO_WALLET_CRYPTO_LIBRARY ${BEST})
+  else ()
+    message("Defaulting to internal crypto library for wallet")
+    set(MONERO_WALLET_CRYPTO_LIBRARY "cn")
+  endif ()
+endif ()
+
+#
+# Configure library target "wallet-crypto" - clients will use this as a
+# library dependency which in turn will depend on the crypto library selected.
+#
+if (${MONERO_WALLET_CRYPTO_LIBRARY} STREQUAL "cn")
+  configure_file(${CMAKE_CURRENT_SOURCE_DIR}/empty.h.in ${MONERO_GENERATED_HEADERS_DIR}/crypto/wallet/ops.h)
+  add_library(wallet-crypto ALIAS cncrypto)
+else ()
+  monero_crypto_generate_header(${MONERO_WALLET_CRYPTO_LIBRARY} "${MONERO_GENERATED_HEADERS_DIR}/crypto/wallet/ops.h")
+  monero_crypto_get_target(${MONERO_WALLET_CRYPTO_LIBRARY} CRYPTO_TARGET)
+  add_library(wallet-crypto $<TARGET_OBJECTS:${CRYPTO_TARGET}>)
+  target_link_libraries(wallet-crypto cncrypto)
+endif ()
+
+
diff --git a/src/crypto/wallet/crypto.h b/src/crypto/wallet/crypto.h
new file mode 100644
index 000000000..a4c5d5a07
--- /dev/null
+++ b/src/crypto/wallet/crypto.h
@@ -0,0 +1,56 @@
+// 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.
+
+#pragma once
+
+#include <cstddef>
+#include "crypto/wallet/ops.h"
+
+namespace crypto {
+  namespace wallet {
+// if C functions defined from external/supercop - cmake generates crypto/wallet/ops.h
+#if defined(monero_crypto_generate_key_derivation)
+      inline
+      bool generate_key_derivation(const public_key &tx_pub, const secret_key &view_sec, key_derivation &out)
+      {
+        return monero_crypto_generate_key_derivation(out.data, tx_pub.data, view_sec.data) == 0;
+      }
+
+      inline
+      bool derive_subaddress_public_key(const public_key &output_pub, const key_derivation &d, std::size_t index, public_key &out)
+      {
+        ec_scalar scalar;
+        derivation_to_scalar(d, index, scalar);
+        return monero_crypto_generate_subaddress_public_key(out.data, output_pub.data, scalar.data) == 0;
+      }
+#else
+    using ::crypto::generate_key_derivation;
+    using ::crypto::derive_subaddress_public_key;
+#endif
+  }
+}
diff --git a/src/crypto/wallet/empty.h.in b/src/crypto/wallet/empty.h.in
new file mode 100644
index 000000000..ac252e1bd
--- /dev/null
+++ b/src/crypto/wallet/empty.h.in
@@ -0,0 +1,31 @@
+// 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.
+
+#pragma once
+
+// Left empty so internal cryptonote crypto library is used.
diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt
index 42dba2ebb..ff2afba4b 100644
--- a/src/device/CMakeLists.txt
+++ b/src/device/CMakeLists.txt
@@ -72,6 +72,7 @@ target_link_libraries(device
     ${HIDAPI_LIBRARIES}
     cncrypto
     ringct_basic
+    wallet-crypto
     ${OPENSSL_CRYPTO_LIBRARIES}
     ${Boost_SERIALIZATION_LIBRARY}
   PRIVATE
diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp
index 7e054af35..096cb35ba 100644
--- a/src/device/device_default.cpp
+++ b/src/device/device_default.cpp
@@ -32,6 +32,7 @@
 
 #include "device_default.hpp"
 #include "int-util.h"
+#include "crypto/wallet/crypto.h"
 #include "cryptonote_basic/account.h"
 #include "cryptonote_basic/subaddress_index.h"
 #include "cryptonote_core/cryptonote_tx_utils.h"
@@ -120,7 +121,7 @@ namespace hw {
         /* ======================================================================= */
 
         bool device_default::derive_subaddress_public_key(const crypto::public_key &out_key, const crypto::key_derivation &derivation, const std::size_t output_index, crypto::public_key &derived_key) {
-            return crypto::derive_subaddress_public_key(out_key, derivation, output_index,derived_key);
+            return crypto::wallet::derive_subaddress_public_key(out_key, derivation, output_index,derived_key);
         }
 
         crypto::public_key device_default::get_subaddress_spend_public_key(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) {
@@ -236,7 +237,7 @@ namespace hw {
         }
 
         bool device_default::generate_key_derivation(const crypto::public_key &key1, const crypto::secret_key &key2, crypto::key_derivation &derivation) {
-            return crypto::generate_key_derivation(key1, key2, derivation);
+            return crypto::wallet::generate_key_derivation(key1, key2, derivation);
         }
 
         bool device_default::derivation_to_scalar(const crypto::key_derivation &derivation, const size_t output_index, crypto::ec_scalar &res){
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@
+