Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.1] Additions to modular arithmetic test cases #50

Merged
merged 1 commit into from
Aug 3, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()