// Copyright (c) 2021-2024 Zano Project // Copyright (c) 2021-2024 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #pragma once #include #include "crypto/crypto-sugar.h" // just for intellysense uint64_t get_bits_v1(const scalar_t& s, uint8_t bit_index_first, uint8_t bits_count) { if (bits_count == 0 || bits_count > 64) return 0; unsigned int bit_index_last = bit_index_first + bits_count - 1; if (bit_index_last > 255) bit_index_last = 255; uint64_t result_mask = ((1ull << (bits_count - 1)) - 1) << 1 | 1; // (just because 1ull << 64 in undefined behaviour, not a 0 as one would expect) uint64_t result = s.m_u64[bit_index_first >> 6] >> (bit_index_first & 63); if (bits_count > (bit_index_last & 63) + 1) result |= s.m_u64[bit_index_last >> 6] << (bits_count - (bit_index_last & 63) - 1); return result & result_mask; } TEST(crypto, ge_precomp) { //precomp_data_t G_precomp = {}; //construct_precomp_data(G_precomp, c_point_G); //std::cout << "size of G_precomp: " << sizeof G_precomp << " bytes" << ENDL; //for(size_t i = 0; i < 32; ++i) // for(size_t j = 0; j < 8; ++j) // std::cout << "i: " << i << ", j: " << j << ", precomp: " << ENDL << G_precomp[i][j] << ENDL; precomp_data_t H_precomp = {}; construct_precomp_data(H_precomp, c_point_H); auto check_for_x = [&](const scalar_t& x) -> bool { point_t P; ge_scalarmult_precomp_vartime(&P.m_p3, H_precomp, x.m_s); return P == x * c_point_H; }; ASSERT_TRUE(check_for_x(c_scalar_0)); ASSERT_TRUE(check_for_x(c_scalar_1)); ASSERT_TRUE(check_for_x(c_scalar_1div8)); ASSERT_TRUE(check_for_x(c_scalar_Lm1)); ASSERT_TRUE(check_for_x(c_scalar_L)); for(size_t i = 0; i < 1000; ++i) { scalar_t x = scalar_t::random(); ASSERT_TRUE(check_for_x(x)); } return true; } TEST(perf, primitives) { struct helper { static void make_rnd_indicies(std::vector& v, size_t size) { v.resize(size); for (size_t i = 0; i < size; ++i) v[i] = i; std::shuffle(v.begin(), v.end(), crypto::uniform_random_bit_generator()); }; }; struct timer_t { std::chrono::high_resolution_clock::time_point m_tp{}; uint64_t m_t{}; uint64_t m_div_coeff{ 1 }; void start(uint64_t div_coeff = 1) { m_tp = std::chrono::high_resolution_clock::now(); m_div_coeff = div_coeff; } void stop() { m_t = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_tp).count(); } uint64_t get_time_mcs() { return m_div_coeff == 1 ? m_t : m_t / m_div_coeff; } }; typedef uint64_t(*run_func_t)(timer_t& t, size_t rounds); auto run = [](const std::string& title, size_t rounds, run_func_t cb) { uint64_t result; timer_t t_warmup, t, t_total; t_total.start(); result = cb(t_warmup, rounds); result += cb(t, rounds); t_total.stop(); double run_time_mcs_x_100 = double(uint64_t(t.get_time_mcs() / (rounds / 100))); LOG_PRINT_L0(std::left << std::setw(40) << title << std::setw(7) << rounds << " rnds -> " << std::right << std::setw(7) << std::fixed << std::setprecision(2) << run_time_mcs_x_100 / 100.0 << " mcs avg. (gross: " << std::fixed << std::setprecision(2) << double(t_total.get_time_mcs()) / 1000.0 << " ms), result hash: " << result); }; #define HASH_64_VEC(vec_var_name) hash_64(vec_var_name.data(), vec_var_name.size() * sizeof(vec_var_name[0])) LOG_PRINT_L0(ENDL << "hash functions:"); struct run_cn_fash_hash { static uint64_t run(timer_t& t, size_t rounds, size_t data_size) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); struct bytes64 { unsigned char b[64]; }; std::vector scalars_64(rounds); for (size_t i = 0; i < scalars_64.size(); ++i) crypto::generate_random_bytes(sizeof(bytes64), scalars_64[i].b); std::vector results(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { results[i] = cn_fast_hash(scalars_64[rnd_indecies[i]].b, 64); } t.stop(); return HASH_64_VEC(results); }; }; run("cn_fast_hash(64 bytes)", 1000, [](timer_t& t, size_t rounds) { return run_cn_fash_hash::run(t, rounds, 64ull); }); run("cn_fast_hash(2048 bytes)", 1000, [](timer_t& t, size_t rounds) { return run_cn_fash_hash::run(t, rounds, 2048ull); }); LOG_PRINT_L0(ENDL << "native crypto primitives:"); run("sc_reduce", 30000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); struct bytes64 { unsigned char b[64]; }; std::vector scalars_64(rounds); for (size_t i = 0; i < scalars_64.size(); ++i) crypto::generate_random_bytes(sizeof(bytes64), scalars_64[i].b); t.start(); for (size_t i = 0; i < rounds; ++i) { sc_reduce(scalars_64[rnd_indecies[i]].b); } t.stop(); return HASH_64_VEC(scalars_64); }); run("sc_reduce32", 30000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector scalars(rounds); for (size_t i = 0; i < scalars.size(); ++i) crypto::generate_random_bytes(sizeof(crypto::ec_scalar), scalars[i].data); t.start(); for (size_t i = 0; i < rounds; ++i) { sc_reduce32((unsigned char*)&scalars[rnd_indecies[i]].data); } t.stop(); return HASH_64_VEC(scalars); }); run("sc_mul", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector a(rounds), b(rounds); for (size_t i = 0; i < rounds; ++i) { a[i].make_random(); b[i].make_random(); } std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) sc_mul(result[rnd_indecies[i]].m_s, a[rnd_indecies[i]].m_s, b[rnd_indecies[i]].m_s); t.stop(); return HASH_64_VEC(result); }); run("sc_add", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector a(rounds), b(rounds); for (size_t i = 0; i < rounds; ++i) { a[i].make_random(); b[i].make_random(); } std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) sc_add(result[i].m_s, a[rnd_indecies[i]].m_s, b[rnd_indecies[i]].m_s); t.stop(); return HASH_64_VEC(result); }); run("sc_mul + sc_add", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector a(rounds), b(rounds), c(rounds); for (size_t i = 0; i < rounds; ++i) { a[i].make_random(); b[i].make_random(); c[i].make_random(); } std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { scalar_t tmp; sc_mul(tmp.m_s, a[rnd_indecies[i]].m_s, b[rnd_indecies[i]].m_s); // tmp = a * b sc_add(result[i].m_s, tmp.m_s, c[rnd_indecies[i]].m_s); // result = tmp + c } t.stop(); return HASH_64_VEC(result); }); run("sc_muladd", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector a(rounds), b(rounds), c(rounds); for (size_t i = 0; i < rounds; ++i) { a[i].make_random(); b[i].make_random(); c[i].make_random(); } std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) sc_muladd(result[i].m_s, a[rnd_indecies[i]].m_s, b[rnd_indecies[i]].m_s, c[rnd_indecies[i]].m_s); t.stop(); return HASH_64_VEC(result); }); run("ge_p3_tobytes", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points_p3(rounds); ge_scalarmult_base(&points_p3[0], c_scalar_1.data()); for (size_t i = 1; i < points_p3.size(); ++i) ge_bytes_hash_to_ec_32(&points_p3[i], (const unsigned char*)&points_p3[i - 1].X); // P_{i+1} = Hp(P_i.X) std::vector points(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_p3_tobytes((unsigned char*)points[i].data, &points_p3[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points); }); run("ge_frombytes_vartime(p3)", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); point_t P = c_point_G; std::vector points_p3_bytes(rounds); for (size_t i = 0; i < points_p3_bytes.size(); ++i) { P = hash_helper_t::hp(P); ge_p3_tobytes((unsigned char*)&points_p3_bytes[i], &P.m_p3); } std::vector points(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_frombytes_vartime(&points[i], (unsigned char*)&points_p3_bytes[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points); }); run("ge_p3_to_cached(p3)", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points_p3(rounds); ge_scalarmult_base(&points_p3[0], c_scalar_1.data()); for (size_t i = 1; i < points_p3.size(); ++i) ge_bytes_hash_to_ec_32(&points_p3[i], (const unsigned char*)&points_p3[i - 1].X); // P_{i+1} = Hp(P_i.X) std::vector points_cached(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_p3_to_cached(&points_cached[i], &points_p3[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_cached); }); run("ge_add(p1p1 = p3 + cached)", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points_cached(rounds); point_t p = scalar_t::random() * c_point_G; for (size_t i = 0; i < rnd_indecies.size(); ++i) { ge_p3_to_cached(&points_cached[i], &p.m_p3); p = p + p; } ge_p3 Q; scalar_t s_rnd = scalar_t::random(); ge_scalarmult_base(&Q, &s_rnd.m_s[0]); std::vector results(rnd_indecies.size()); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_add(&results[i], &Q, &points_cached[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(results); }); run("ge_p1p1_to_p3(p1p1)", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); ge_cached G; ge_p3_to_cached(&G, &c_point_G.m_p3); std::vector points_p1p1(rounds); ge_add(&points_p1p1[0], &c_point_G.m_p3, &G); for (size_t i = 1; i < points_p1p1.size(); ++i) { ge_p3 p3; ge_p1p1_to_p3(&p3, &points_p1p1[i - 1]); ge_add(&points_p1p1[i], &p3, &G); } std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_p1p1_to_p3(&points_p3[i], &points_p1p1[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult()", 5000, [](timer_t& t, size_t rounds) { //rounds -= rounds % 8; std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { //scalar_t x = x + x + x; scalar_t x; x.make_random(); memcpy(&scalars[i].data, x.data(), 32); } point_t p = scalar_t::random() * c_point_G; //std::vector points_p2(rounds); std::vector points_p3(rounds); // warmup round //for (size_t i = 0; i < rounds; ++i) // ge_scalarmult((ge_p2*)&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult((ge_p2*)&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 3], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 3]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 0], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 0]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 1], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 1]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 2], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 2]], &p.m_p3); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult() (2)", 5000, [](timer_t& t, size_t rounds) { //rounds -= rounds % 8; std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { //scalar_t x = x + x + x; scalar_t x; x.make_random(); memcpy(&scalars[i].data, x.data(), 32); } point_t p = scalar_t::random() * c_point_G; //std::vector points_p2(rounds); std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult((ge_p2*)&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 3], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 3]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 0], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 0]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 1], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 1]], &p.m_p3); //ge_scalarmult(&points_p2[i * 4 + 2], (const unsigned char*)&scalars[rnd_indecies[i * 4 + 2]], &p.m_p3); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult_p3()", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { //scalar_t x = x + x + x; scalar_t x; x.make_random(); memcpy(&scalars[i].data, x.data(), 32); } point_t p = scalar_t::random() * c_point_G; std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_p3(&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult_vartime_p3()", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { //scalar_t x = x + x + x; scalar_t x; x.make_random(); memcpy(&scalars[i].data, x.data(), 32); } point_t p = scalar_t::random() * c_point_G; //memcpy(&scalars[rnd_indecies[0]], scalar_t(1).data(), 32); std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_vartime_p3(&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult_vartime_p3_v2()", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { //scalar_t x = x + x + x; scalar_t x; x.make_random(); memcpy(&scalars[i].data, x.data(), 32); } point_t p = scalar_t::random() * c_point_G; std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_vartime_p3_v2(&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]], &p.m_p3); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult_base()", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { scalar_t x = x + x + x; memcpy(&scalars[i].data, x.data(), 32); } std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_base(&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_p3); }); run("construct_precomp_data()", 300, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); unsigned char s[32] = {}; std::vector random_points(rounds); for (size_t i = 0; i < rounds; ++i) { s[0] = i; ge_p2 p2; ge_fromfe_frombytes_vartime(&p2, s); ge_p2_to_p3(&random_points[i].m_p3, &p2); } std::vector points_p3(rounds); precomp_data_t precomp_data; uint64_t result = 0; t.start(); for (size_t i = 0; i < rounds; ++i) { construct_precomp_data(precomp_data, random_points[rnd_indecies[i]]); result ^= (precomp_data[1][1].xy2d[1] + precomp_data[31][7].xy2d[9]); } t.stop(); return result; }); run("ge_scalarmult_precomp_vartime()", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { scalar_t x = x + x + x; memcpy(&scalars[i].data, x.data(), 32); } precomp_data_t precomp_data; construct_precomp_data(precomp_data, x * c_point_X); std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_precomp_vartime(&points_p3[i], precomp_data, (const unsigned char*)&scalars[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_scalarmult_base_vartime()", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_t x; x.make_random(); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) { scalar_t x = x + x + x; memcpy(&scalars[i].data, x.data(), 32); } std::vector points_p3(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_scalarmult_base_vartime(&points_p3[i], (const unsigned char*)&scalars[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_p3); }); run("ge_mul8_p3()", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points_p3(rounds); ge_scalarmult_base(&points_p3[0], c_scalar_1.data()); for (size_t i = 1; i < points_p3.size(); ++i) ge_bytes_hash_to_ec_32(&points_p3[i], (const unsigned char*)&points_p3[i - 1].X); // P_{i+1} = Hp(P_i.X) std::vector points_result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_mul8_p3(&points_result[i], &points_p3[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_result); }); run("ge_mul8()", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); point_t p = scalar_t::random() * c_point_G; std::vector points_p2(rounds); ge_p3_to_p2(&points_p2[0], &p.m_p3); ge_p1p1 p1; for (size_t i = 0; i < points_p2.size() - 1; ++i) { ge_p2_dbl(&p1, &points_p2[i]); ge_p1p1_to_p2(&points_p2[i + 1], &p1); } std::vector points_result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { ge_mul8(&points_result[i], &points_p2[rnd_indecies[i]]); } t.stop(); return HASH_64_VEC(points_result); }); LOG_PRINT_L0(ENDL << "new primitives:"); run("point_t + point_t", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points(rounds); point_t p = c_point_G; for (size_t i = 0; i < rounds; ++i) { points[i] = p; p = p + p; } std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { result[i] = points[rnd_indecies[i]] + p; } t.stop(); return HASH_64_VEC(result); }); run("sclar_t * point_t", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) scalars[i].make_random(); point_t p = scalar_t::random() * c_point_G; std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { result[i] = scalars[rnd_indecies[i]] * p; } t.stop(); return HASH_64_VEC(result); }); run("sclar_t * point_g_t", 5000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) scalars[i].make_random(); std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { result[i] = scalars[rnd_indecies[i]] * c_point_G; } t.stop(); return HASH_64_VEC(result); }); run("sclar_t * scalar_t", 50000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) scalars[i].make_random(); scalar_t s = scalar_t::random(); std::vector result(rounds); t.start(4); for (size_t i = 0; i < rounds; ++i) { result[i] = scalars[rnd_indecies[i]] * s * s * s * s; } t.stop(); return HASH_64_VEC(result); }); run("sclar_t / scalar_t", 10000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector scalars(rounds); for (size_t i = 0; i < rounds; ++i) scalars[i].make_random(); scalar_t s = scalar_t::random(); std::vector result(rounds); t.start(2); for (size_t i = 0; i < rounds; ++i) { result[i] = scalars[rnd_indecies[i]] / s / s; } t.stop(); return HASH_64_VEC(result); }); run("mul_plus_G", 2000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); std::vector points(rounds); point_t p = c_point_G; for (size_t i = 0; i < rounds; ++i) { points[i] = p; p = p + p; } scalar_t a, b; a.make_random(); b.make_random(); std::vector result(rounds); t.start(2); for (size_t i = 0; i < rounds; ++i) { result[i] = points[rnd_indecies[i]].mul_plus_G(a, b).mul_plus_G(a, b); } t.stop(); return HASH_64_VEC(result); }); run("get_bits x 10", 20000, [](timer_t& t, size_t rounds) { std::vector rnd_indecies; helper::make_rnd_indicies(rnd_indecies, rounds); scalar_vec_t data; data.resize_and_make_random(rounds); std::vector result(rounds); t.start(); for (size_t i = 0; i < rounds; ++i) { auto& x = data[rnd_indecies[i]]; result[i] = x.get_bits(x.m_s[11], x.m_s[21] % 65) ^ x.get_bits(x.m_s[12], x.m_s[22] % 65) ^ x.get_bits(x.m_s[13], x.m_s[23] % 65) ^ x.get_bits(x.m_s[14], x.m_s[24] % 65) ^ x.get_bits(x.m_s[15], x.m_s[25] % 65) ^ x.get_bits(x.m_s[16], x.m_s[26] % 65) ^ x.get_bits(x.m_s[17], x.m_s[27] % 65) ^ x.get_bits(x.m_s[18], x.m_s[28] % 65) ^ x.get_bits(x.m_s[19], x.m_s[29] % 65) ^ x.get_bits(x.m_s[20], x.m_s[30] % 65); } t.stop(); return HASH_64_VEC(result); }); return true; } // TEST //////////////////////////////////////////////////////////////////////////////// ///////////////// v3 ///////////////// v4 template bool msm_and_check_zero_pippenger_v4(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand, size_t c) { // TODO: with c = 8 and with direct access got much worse result than with c = 7 and get_bits(), consider checking again for bigger datasets (N>256) CHECK_AND_ASSERT_MES(g_scalars.size() <= CT::c_bpp_mn_max, false, "g_scalars oversized"); CHECK_AND_ASSERT_MES(h_scalars.size() <= CT::c_bpp_mn_max, false, "h_scalars oversized"); CHECK_AND_ASSERT_MES(c < 10, false, "c is too big"); size_t C = 1ull << c; // k_max * c + (c-1) >= max_bit_idx // // max_bit_idx - (c - 1) max_bit_idx - (c - 1) + (c - 1) max_bit_idx // k_max = ceil ( --------------------- ) = floor ( ------------------------------ ) = floor ( ----------- ) // c c c const size_t b = 253; // the maximum number of bits in x https://eprint.iacr.org/2022/999.pdf TODO: we may also scan for maximum bit used in all the scalars if all the scalars are small const size_t max_bit_idx = b - 1; const size_t k_max = max_bit_idx / c; const size_t K = k_max + 1; std::unique_ptr buckets( new point_t[C * K] ); std::vector buckets_inited(C * K); // first loop, calculate partial bucket sums for (size_t n = 0; n < g_scalars.size(); ++n) { for (size_t k = 0; k < K; ++k) { uint64_t l = g_scalars[n].get_bits(k * c, c); // l in [0; 2^c-1] if (l != 0) { size_t bucket_id = l * K + k; if (buckets_inited[bucket_id]) buckets[bucket_id] += CT::get_generator(false, n); else { buckets[bucket_id] = CT::get_generator(false, n); buckets_inited[bucket_id] = true; } } } } for (size_t n = 0; n < h_scalars.size(); ++n) { for (size_t k = 0; k < K; ++k) { uint64_t l = h_scalars[n].get_bits(k * c, c); // l in [0; 2^c-1] if (l != 0) { size_t bucket_id = l * K + k; if (buckets_inited[bucket_id]) buckets[bucket_id] += CT::get_generator(true, n); else { buckets[bucket_id] = CT::get_generator(true, n); buckets_inited[bucket_id] = true; } } } } // the second loop // S[l, k] = S[l-1, k] + B[l, k] // G[k] = sum{1..C-1} S[l, k] std::unique_ptr Sk( new point_t[K] ); std::vector Sk_inited(K); std::unique_ptr Gk( new point_t[K] ); std::vector Gk_inited(K); for (size_t l = C - 1; l > 0; --l) { for (size_t k = 0; k < K; ++k) { size_t bucket_id = l * K + k; if (buckets_inited[bucket_id]) { if (Sk_inited[k]) Sk[k] += buckets[bucket_id]; else { Sk[k] = buckets[bucket_id]; Sk_inited[k] = true; } } if (Sk_inited[k]) { if (Gk_inited[k]) Gk[k] += Sk[k]; else { Gk[k] = Sk[k]; Gk_inited[k] = true; } } } } // the third loop: Horner’s rule point_t result = Gk_inited[K - 1] ? Gk[K - 1] : c_point_0; for (size_t k = K - 2; k != SIZE_MAX; --k) { result.modify_mul_pow_2(c); if (Gk_inited[k]) result += Gk[k]; } result += summand; if (!result.is_zero()) { LOG_PRINT_L0("msm result is non zero: " << result); return false; } return true; } //////////////////////////////////////////////////////////////////////////////// //template //struct mes_msm_and_check_zero_pippenger_v1 //{ // static bool msm_and_check_zero(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand, size_t c) // { // return msm_and_check_zero_pippenger_v1(g_scalars, h_scalars, summand, c); // } //}; // //template //struct mes_msm_and_check_zero_pippenger_v2 //{ // static bool msm_and_check_zero(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand, size_t c) // { // return msm_and_check_zero_pippenger_v2(g_scalars, h_scalars, summand, c); // } //}; template struct mes_msm_and_check_zero_pippenger_v3 { static bool msm_and_check_zero(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand, size_t c) { return msm_and_check_zero_pippenger_v3(g_scalars, h_scalars, summand, c); } }; template struct mes_msm_and_check_zero_pippenger_v4 { static bool msm_and_check_zero(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand, size_t c) { return msm_and_check_zero_pippenger_v4(g_scalars, h_scalars, summand, c); } }; struct pme_runner_i { virtual ~pme_runner_i() {} virtual bool iteration(bool warmup) = 0; }; template typename selector_t> struct pme_runner_t : public pme_runner_i { pme_runner_t(const char* testname_, size_t pip_partition_bits_c) : testname(testname_) , pip_partition_bits_c(pip_partition_bits_c) { testname += std::string(", ") + std::string(typeid(selector_t).name()).erase(0, 11) + std::string(", c = ") + epee::string_tools::num_to_string_fast(pip_partition_bits_c); std::cout << testname << ENDL; } virtual ~pme_runner_t() { if (timings.empty()) return; uint64_t median = 0; auto median_it = timings.begin() + timings.size() / 2; std::nth_element(timings.begin(), median_it, timings.end()); median = *median_it; if (timings.size() % 2 == 0) { auto max_it = std::max_element(timings.begin(), median_it); median = (median + *max_it) / 2; } uint64_t total_time = std::accumulate(timings.begin(), timings.end(), 0); std::cout << std::left << std::setw(100) << testname << " : " << std::setw(5) << median << " (median), " << std::setw(5) << total_time / timings.size() << " (avg), mcs" << ENDL; } virtual bool iteration(bool warmup) { scalar_vec_t g_scalars, h_scalars; g_scalars.resize_and_make_random(N); g_scalars[0] = c_scalar_Lm1; //std::cout << "bit 251: " << g_scalars[0].get_bit(251) << ", bit 252: " << g_scalars[0].get_bit(252) << ENDL; h_scalars.resize_and_make_random(N); point_t sum = c_point_0; for(size_t i = 0; i < N; ++i) { //g_scalars[i].m_u64[3] = 0; //h_scalars[i].m_u64[3] = 0; //g_scalars[i].m_s[31] = 0; //h_scalars[i].m_s[31] = 0; sum += g_scalars[i] * CT::get_generator(false, i) + h_scalars[i] * CT::get_generator(true, i); } TIME_MEASURE_START(t); bool r = selector_t::msm_and_check_zero(g_scalars, h_scalars, -sum, pip_partition_bits_c); TIME_MEASURE_FINISH(t); ASSERT_TRUE(r); if (!warmup) timings.push_back(t); return true; } std::vector timings; std::string testname; size_t pip_partition_bits_c; }; TEST(perf, msm) { std::deque> runners; //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 1)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 2)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 3)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 4)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 5)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 6)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 8)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 9)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 1)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 2)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 3)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 4)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 5)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 6)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 8)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 9)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 1)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 2)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 3)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 4)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 5)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 6)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 8)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v2> >("Zarcanum, BPPE, 128", 9)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 1)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 2)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 3)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 4)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 5)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 6)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 8)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v2> >("ZC out, BPP, 256", 9)); runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v3> >("Zarcanum, BPPE, 128 +++++++++++", 7)); runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v3> >("ZC out, BPP, 256 +++++++++++", 7)); runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v4> >("Zarcanum, BPPE, 128 ###########", 7)); runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v4> >("ZC out, BPP, 256 ###########", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 7)); //runners.emplace_front(std::make_unique< pme_runner_t<256, bpp_crypto_trait_ZC_out, mes_msm_and_check_zero_pippenger_v1> >("ZC out, BPP, 256", 7)); std::cout << "warm up..." << ENDL; size_t runs_count = 30; for(size_t k = 0; k < runs_count; ++k) { for(auto& runner : runners) ASSERT_TRUE(runner->iteration(true)); } runs_count = 200; for(size_t k = 0; k < runs_count; ++k) { for(auto& runner : runners) ASSERT_TRUE(runner->iteration(false)); size_t done_percent = 100 * k / runs_count; if (100 * (k + 1) / runs_count > done_percent && done_percent % 5 == 0) std::cout << done_percent << " %" << ENDL; } return true; } template bool perf_generators_runner(const T& generator, const char* title) { const size_t warmup_rounds = 20; const size_t rounds = 500; const size_t inner_rounds = 128; uint64_t h = 0; std::vector timings; size_t N = 1024; scalar_vec_t scalars; scalars.resize_and_make_random(N); std::vector points(N); for(size_t i = 0; i < warmup_rounds; ++i) for(size_t j = 0; j < inner_rounds; ++j) points[(i + j) % N] = scalars[(i + j) % N] * generator; h = hash_64(points.data(), points.size() * sizeof(point_t)); for(size_t i = 0; i < rounds; ++i) { TIME_MEASURE_START(t); for(size_t j = 0; j < inner_rounds; ++j) points[(i + j) % N] = scalars[(i + j) % N] * generator; TIME_MEASURE_FINISH(t); timings.push_back(t); } h ^= hash_64(points.data(), points.size() * sizeof(point_t)); std::cout << std::left << std::setw(20) << title << " : " << std::setw(5) << std::fixed << std::setprecision(1) << (double)epee::misc_utils::median(timings) / inner_rounds << " mcs, hash = " << h << ENDL; return true; } TEST(perf, generators) { #define TEST_GENERATOR(G) ASSERT_TRUE(perf_generators_runner(G, #G)) TEST_GENERATOR(c_point_0); TEST_GENERATOR(c_point_G); TEST_GENERATOR(c_point_H); TEST_GENERATOR(c_point_H2); TEST_GENERATOR(c_point_U); TEST_GENERATOR(c_point_X); TEST_GENERATOR(c_point_H_plus_G); TEST_GENERATOR(c_point_H_minus_G); return true; } bool old_point_eq_operator_bugous(const point_t& lhs, const point_t& rhs) { // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) fe lrecip, lx, ly; fe rrecip, rx, ry; fe_invert(lrecip, lhs.m_p3.Z); fe_invert(rrecip, rhs.m_p3.Z); fe_mul(lx, lhs.m_p3.X, lrecip); fe_mul(rx, rhs.m_p3.X, rrecip); if (memcmp(&lx, &rx, sizeof lx) != 0) return false; fe_mul(ly, lhs.m_p3.Y, lrecip); fe_mul(ry, rhs.m_p3.Y, rrecip); if (memcmp(&ly, &ry, sizeof ly) != 0) return false; return true; } bool old_point_eq_operator(const point_t& lhs, const point_t& rhs) { // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) fe lrecip, lx, ly, dx; fe rrecip, rx, ry, dy; fe_invert(lrecip, lhs.m_p3.Z); fe_invert(rrecip, rhs.m_p3.Z); fe_mul(lx, lhs.m_p3.X, lrecip); fe_mul(rx, rhs.m_p3.X, rrecip); fe_sub(dx, lx, rx); if (fe_isnonzero(dx) != 0) return false; fe_mul(ly, lhs.m_p3.Y, lrecip); fe_mul(ry, rhs.m_p3.Y, rrecip); fe_sub(dy, ly, ry); if (fe_isnonzero(dy) != 0) return false; return true; } TEST(perf, point_eq_vs_iszero) { const size_t warmup_rounds = 20; const size_t rounds = 200; const size_t inner_rounds = 64; std::vector timings1, timings2; size_t N = inner_rounds - twin_point_pairs.size() * 2; // number of random points scalar_vec_t scalars; scalars.resize_and_make_random(N); std::vector points(N); std::transform(scalars.cbegin(), scalars.cend(), points.begin(), [](const scalar_t& s){ return s * c_point_G; }); // add twin points for(auto& p : twin_point_pairs) points.push_back(p.first), points.push_back(p.second); ASSERT_EQ(points.size(), inner_rounds); // and shuffle std::shuffle(points.begin(), points.end(), crypto::uniform_random_bit_generator{}); for(size_t i = 0; i < rounds + warmup_rounds; ++i) { std::vector results1(inner_rounds), results2(inner_rounds); TIME_MEASURE_START(t1); for(size_t j = 0; j < inner_rounds; ++j) for(size_t k = j + 1; k < inner_rounds; ++k) results1[j] ^= uint8_t(old_point_eq_operator(points[j], points[k]) ? j : k); TIME_MEASURE_FINISH(t1); uint64_t h1 = hash_64(results1.data(), results1.size() * sizeof(results1[0])); TIME_MEASURE_START(t2); for(size_t j = 0; j < inner_rounds; ++j) for(size_t k = j + 1; k < inner_rounds; ++k) results2[j] ^= uint8_t((points[j] - points[k]).is_zero() ? j : k); TIME_MEASURE_FINISH(t2); uint64_t h2 = hash_64(results2.data(), results2.size() * sizeof(results2[0])); ASSERT_EQ(h1, h2); if (i >= warmup_rounds) { timings1.push_back(t1); timings2.push_back(t2); } } std::cout << "After " << rounds << " rounds:" << ENDL << "point_t operator== : " << epee::misc_utils::median(timings1) << " mcs" << ENDL << "point_t is_zero() : " << epee::misc_utils::median(timings2) << " mcs" << ENDL; return true; } TEST(perf, buff_to_hex) { std::vector in_buffs; std::vector out_hexs; std::vector timings1, timings2; size_t N = 10000; in_buffs.reserve(N); out_hexs.reserve(N); for(size_t i = 0; i < N; ++i) { size_t len = (crypto::rand() % 128) + 1; // [1; 128] std::string& buff = in_buffs.emplace_back(); buff.resize(len); generate_random_bytes(len, buff.data()); } size_t rounds = 100, warmup_rounds = 20; for(size_t i = 0; i < warmup_rounds + rounds; ++i) { out_hexs.clear(); TIME_MEASURE_START(t1); for(size_t j = 0; j < N; ++j) out_hexs.push_back(epee::string_tools::buff_to_hex_nodelimer(in_buffs[j])); TIME_MEASURE_FINISH(t1); uint64_t h1 = 0; for(size_t j = 0; j < N; ++j) h1 ^= hash_64(out_hexs[j].data(), out_hexs[j].size()); out_hexs.clear(); TIME_MEASURE_START(t2); for(size_t j = 0; j < N; ++j) out_hexs.push_back(crypto::buff_to_hex(in_buffs[j].data(), in_buffs[j].size())); TIME_MEASURE_FINISH(t2); uint64_t h2 = 0; for(size_t j = 0; j < N; ++j) h2 ^= hash_64(out_hexs[j].data(), out_hexs[j].size()); ASSERT_EQ(h1, h2); if (i >= warmup_rounds) { timings1.push_back(t1); timings2.push_back(t2); } } std::cout << "After " << rounds << " rounds:" << ENDL << "epee::string_tools::buff_to_hex_nodelimer : " << epee::misc_utils::median(timings1) << " mcs" << ENDL << "crypto::buff_to_hex : " << epee::misc_utils::median(timings2) << " mcs" << ENDL; return true; }