From 0cc2b8c2db9343ba5258adde868c76d15b921edf Mon Sep 17 00:00:00 2001 From: Areg Hayrapetian Date: Tue, 2 Aug 2022 17:13:16 -0700 Subject: [PATCH] Additions to modular arithmetic test cases. This is part of the effort to add subjective limits to the mod_exp host function (see eosnetworkfoundation/mandel#361). Adds a few additional test vectors to existing modexp test case. Adds a modexp_benchmarking test case which is not actually a test but rather a way to get measurements on the time it takes to do modular exponentiation with varying bit sizes to figure out what the appropriate limit should be for the mod_exp host function. --- test/crypto/test_modular_arithmetic.cpp | 137 ++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/test/crypto/test_modular_arithmetic.cpp b/test/crypto/test_modular_arithmetic.cpp index 5ec8ca5..54257ca 100644 --- a/test/crypto/test_modular_arithmetic.cpp +++ b/test/crypto/test_modular_arithmetic.cpp @@ -6,6 +6,10 @@ #include #include +#include +#include +#include + using namespace fc; #include "test_utils.hpp" @@ -22,6 +26,7 @@ std::ostream& operator<<(std::ostream& st, const std::variant result(num_bytes); + + uint_fast32_t v = 0; + for(int byte_pos = 0, end = result.size(); byte_pos < end; ++byte_pos) { + if ((byte_pos & 0x03) == 0) { // if divisible by 4 + v = rand_eng(); + } + result[byte_pos] = v & 0xFF; + v >>= 8; + } + + return result; + }; + + static constexpr unsigned int num_trials = 100; // 10000 + + static_assert(num_trials > 0); + + static constexpr unsigned int start_num_bytes = 128; // 64 + static constexpr unsigned int end_num_bytes = 256; // 512 + static constexpr unsigned int delta_num_bytes = 128; // 64 + + static_assert(start_num_bytes <= end_num_bytes); + static_assert(delta_num_bytes > 0); + static_assert((end_num_bytes - start_num_bytes) % delta_num_bytes == 0); + + static constexpr unsigned num_slots = (end_num_bytes - start_num_bytes) / delta_num_bytes + 1; + + struct statistics { + int64_t min_time_ns; + int64_t max_time_ns; + int64_t avg_time_ns; + }; + + std::vector stats(num_slots); + + for (unsigned int n = start_num_bytes, slot = 0; n <= end_num_bytes; n += delta_num_bytes, ++slot) { + int64_t min_duration_ns = std::numeric_limits::max(); + int64_t max_duration_ns = 0; + int64_t total_duration_ns = 0; + + for (unsigned int trial = 0; trial < num_trials; ++trial) { + auto base = generate_random_bytes(r, n); + auto exponent = generate_random_bytes(r, n); + auto modulus = generate_random_bytes(r, n); + + auto start_time = std::chrono::steady_clock::now(); + + auto res = fc::modexp(base, exponent, modulus); + + auto end_time = std::chrono::steady_clock::now(); + + int64_t duration_ns = std::chrono::duration_cast(end_time - start_time).count(); + + //ilog("(${base})^(${exp}) % ${mod} = ${result}", + // ("base", base)("exp", exponent)("mod", modulus)("result", std::get(res)) + // ); + + //ilog("slot ${slot}: mod_exp took ${duration} ns", ("slot", slot)("duration", duration_ns)); + + min_duration_ns = std::min(min_duration_ns, duration_ns); + max_duration_ns = std::max(max_duration_ns, duration_ns); + total_duration_ns += duration_ns; + } + + stats[slot] = statistics{ + .min_time_ns = min_duration_ns, + .max_time_ns = max_duration_ns, + .avg_time_ns = (total_duration_ns / num_trials), + }; + + ilog("Completed random runs of mod_exp with ${bit_width}-bit width values. Min time: ${min} ns; Average time: ${avg} ns; Max time: ${max} ns.", + ("bit_width", n*8)("min", stats[slot].min_time_ns)("avg", stats[slot].avg_time_ns)("max", stats[slot].max_time_ns) + ); + } + + // Running the above benchmark (using commented values for num_trials and *_num_bytes) with a release build on an AMD 3.4 GHz CPU + // provides average durations for executing mod_exp for increasing bit sizes for the value. + + // For example: with 512-bit values, the average duration is approximately 40 microseconds; with 1024-bit values, the average duration + // is approximately 260 microseconds; with 2048-bit values, the average duration is approximately 2 milliseconds; and, with 4096-bit + // values, the average duration is approximately 14 milliseconds. + + // It appears that a model of the average time that scales quadratically with the bit size fits the empirically generated data well. + // TODO: See if theoretical analysis of the modular exponentiation algorithm also justifies quadratic scaling. + +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file