Skip to content

Commit

Permalink
Additions to modular arithmetic test cases.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
arhag committed Aug 3, 2022
1 parent c1c28ac commit c13f143
Showing 1 changed file with 137 additions and 0 deletions.
137 changes: 137 additions & 0 deletions test/crypto/test_modular_arithmetic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <fc/crypto/modular_arithmetic.hpp>
#include <fc/utility.hpp>

#include <chrono>
#include <random>
#include <limits>

using namespace fc;
#include "test_utils.hpp"

Expand All @@ -22,6 +26,7 @@ std::ostream& operator<<(std::ostream& st, const std::variant<fc::modular_arithm


BOOST_AUTO_TEST_SUITE(modular_arithmetic)

BOOST_AUTO_TEST_CASE(modexp) try {


Expand Down Expand Up @@ -58,6 +63,45 @@ BOOST_AUTO_TEST_CASE(modexp) try {
modular_arithmetic_error::modulus_len_zero
},

//test4
{
{
"01",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e",
"0000",
},
to_bytes("0000")
},

//test5
{
{
"00",
"00",
"0F",
},
to_bytes("01"),
},

//test6
{
{
"00",
"01",
"0F",
},
to_bytes("00"),
},

//test7
{
{
"01",
"00",
"0F",
},
to_bytes("01"),
},

};

Expand All @@ -75,4 +119,97 @@ BOOST_AUTO_TEST_CASE(modexp) try {

} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(modexp_benchmarking) try {

std::mt19937 r(0x11223344);

auto generate_random_bytes = [](std::mt19937& rand_eng, unsigned int num_bytes) {
std::vector<char> 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<statistics> 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<int64_t>::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<std::chrono::nanoseconds>(end_time - start_time).count();

//ilog("(${base})^(${exp}) % ${mod} = ${result}",
// ("base", base)("exp", exponent)("mod", modulus)("result", std::get<bytes>(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()

0 comments on commit c13f143

Please sign in to comment.