Files
icicle/icicle_v3/tests/test_curve_api.cpp
2024-07-29 16:07:50 +03:00

229 lines
8.6 KiB
C++

#include <gtest/gtest.h>
#include <iostream>
#include <list>
#include "dlfcn.h"
#include "icicle/runtime.h"
#include "icicle/ntt.h"
#include "icicle/msm.h"
#include "icicle/vec_ops.h"
#include "icicle/curves/montgomery_conversion.h"
#include "icicle/curves/curve_config.h"
using namespace curve_config;
using namespace icicle;
using FpMicroseconds = std::chrono::duration<float, std::chrono::microseconds::period>;
#define START_TIMER(timer) auto timer##_start = std::chrono::high_resolution_clock::now();
#define END_TIMER(timer, msg, enable) \
if (enable) \
printf( \
"%s: %.3f ms\n", msg, FpMicroseconds(std::chrono::high_resolution_clock::now() - timer##_start).count() / 1000);
static bool VERBOSE = true;
static inline std::string s_main_target;
static inline std::string s_ref_target;
class CurveApiTest : public ::testing::Test
{
public:
static inline std::list<std::string> s_regsitered_devices;
// SetUpTestSuite/TearDownTestSuite are called once for the entire test suite
static void SetUpTestSuite()
{
#ifdef BACKEND_BUILD_DIR
icicle_load_backend(BACKEND_BUILD_DIR, true);
#endif
// check targets are loaded and choose main and reference targets
auto regsitered_devices = get_registered_devices_list();
ASSERT_GE(regsitered_devices.size(), 2);
const bool is_cuda_registered = is_device_registered("CUDA");
const bool is_cpu_registered = is_device_registered("CPU");
const bool is_cpu_ref_registered = is_device_registered("CPU_REF");
// if cuda is available, want main="CUDA", ref="CPU", otherwise main="CPU", ref="CPU_REF".
s_main_target = is_cuda_registered ? "CUDA" : "CPU";
s_ref_target = is_cuda_registered ? "CPU" : "CPU_REF";
}
static void TearDownTestSuite() {}
// SetUp/TearDown are called before and after each test
void SetUp() override {}
void TearDown() override {}
template <typename T>
void random_scalars(T* arr, uint64_t count)
{
for (uint64_t i = 0; i < count; i++)
arr[i] = i < 1000 ? T::rand_host() : arr[i - 1000];
}
template <typename A, typename P>
void MSM_test()
{
const int logn = 8;
const int batch = 3;
const int N = 1 << logn;
const int total_nof_elemets = batch * N;
auto scalars = std::make_unique<scalar_t[]>(total_nof_elemets);
auto bases = std::make_unique<A[]>(N);
scalar_t::rand_host_many(scalars.get(), total_nof_elemets);
P::rand_host_many(bases.get(), N);
auto result_main = std::make_unique<P[]>(batch);
auto result_ref = std::make_unique<P[]>(batch);
auto run = [&](const std::string& dev_type, P* result, const char* msg, bool measure, int iters) {
Device dev = {dev_type, 0};
icicle_set_device(dev);
std::ostringstream oss;
oss << dev_type << " " << msg;
auto config = default_msm_config();
config.batch_size = batch;
START_TIMER(MSM_sync)
for (int i = 0; i < iters; ++i) {
msm(scalars.get(), bases.get(), N, config, result);
}
END_TIMER(MSM_sync, oss.str().c_str(), measure);
};
run(s_main_target, result_main.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run(s_ref_target, result_ref.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
// Note: avoid memcmp here because projective points may have different z but be equivalent
for (int res_idx = 0; res_idx < batch; ++res_idx) {
ASSERT_EQ(result_main[res_idx], result_ref[res_idx]);
}
}
template <typename T, typename P>
void mont_conversion_test()
{
const int N = 1 << 6;
auto elements = std::make_unique<T[]>(N);
auto main_output = std::make_unique<T[]>(N);
auto ref_output = std::make_unique<T[]>(N);
P::rand_host_many(elements.get(), N);
auto run =
[&](const std::string& dev_type, T* input, T* output, bool into_mont, bool measure, const char* msg, int iters) {
Device dev = {dev_type, 0};
icicle_set_device(dev);
auto config = default_vec_ops_config();
std::ostringstream oss;
oss << dev_type << " " << msg;
START_TIMER(MONTGOMERY)
for (int i = 0; i < iters; ++i) {
ICICLE_CHECK(convert_montgomery(input, N, into_mont, config, output));
}
END_TIMER(MONTGOMERY, oss.str().c_str(), measure);
};
run(s_main_target, elements.get(), main_output.get(), true /*into*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(s_ref_target, elements.get(), ref_output.get(), true /*into*/, VERBOSE /*=measure*/, "to-montgomery", 1);
ASSERT_EQ(0, memcmp(main_output.get(), ref_output.get(), N * sizeof(T)));
run(s_main_target, main_output.get(), main_output.get(), false /*from*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(s_ref_target, ref_output.get(), ref_output.get(), false /*from*/, VERBOSE /*=measure*/, "to-montgomery", 1);
ASSERT_EQ(0, memcmp(main_output.get(), ref_output.get(), N * sizeof(T)));
}
};
TEST_F(CurveApiTest, msm) { MSM_test<affine_t, projective_t>(); }
TEST_F(CurveApiTest, MontConversionAffine) { mont_conversion_test<affine_t, projective_t>(); }
TEST_F(CurveApiTest, MontConversionProjective) { mont_conversion_test<projective_t, projective_t>(); }
#ifdef G2
TEST_F(CurveApiTest, msmG2) { MSM_test<g2_affine_t, g2_projective_t>(); }
TEST_F(CurveApiTest, MontConversionG2Affine) { mont_conversion_test<g2_affine_t, g2_projective_t>(); }
TEST_F(CurveApiTest, MontConversionG2Projective) { mont_conversion_test<g2_projective_t, g2_projective_t>(); }
#endif // G2
#ifdef ECNTT
TEST_F(CurveApiTest, ecntt)
{
const int logn = 5;
const int N = 1 << logn;
auto input = std::make_unique<projective_t[]>(N);
projective_t::rand_host_many(input.get(), N);
scalar_t::rand_host_many(scalars.get(), N);
projective_t::rand_host_many_affine(bases.get(), N);
projective_t result_cpu{};
projective_t result_cpu_dbl_n_add{};
projective_t result_cpu_ref{};
auto run = [&](const std::string& dev_type, projective_t* out, const char* msg, bool measure, int iters) {
Device dev = {dev_type, 0};
icicle_set_device(dev);
const int c = 6;
const int pcf = 10;
auto config = default_msm_config();
config.ext.set("c", c);
config.precompute_factor = pcf;
auto pc_config = default_msm_pre_compute_config();
pc_config.ext.set("c", c);
auto precomp_bases = std::make_unique<affine_t[]>(N*pcf);
START_TIMER(MSM_sync)
for (int i = 0; i < iters; ++i) {
// TODO real test
// msm_precompute_bases(bases.get(), N, 1, default_msm_pre_compute_config(), bases.get());
msm_precompute_bases(bases.get(), N, pcf, pc_config, precomp_bases.get());
msm(scalars.get(), precomp_bases.get(), N, config, result);
}
END_TIMER(MSM_sync, msg, measure);
};
// run("CPU", &result_cpu_dbl_n_add, "CPU msm", false /*=measure*/, 1 /*=iters*/); // warmup
run("CPU", &result_cpu, "CPU msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run("CPU_REF", &result_cpu_ref, "CPU_REF msm", VERBOSE /*=measure*/, 1 /*=iters*/);
// TODO test something
ASSERT_EQ(result_cpu,result_cpu_ref);
}
#endif // ECNTT
template <typename T>
class CurveSanity : public ::testing::Test
{
};
#ifdef G2
typedef testing::Types<projective_t, g2_projective_t> CTImplementations;
#else
typedef testing::Types<projective_t> CTImplementations;
#endif
TYPED_TEST_SUITE(CurveSanity, CTImplementations);
// Note: this is testing host arithmetic. Other tests against CPU backend should guarantee correct device arithmetic too
TYPED_TEST(CurveSanity, CurveSanityTest)
{
auto a = TypeParam::rand_host();
auto b = TypeParam::rand_host();
ASSERT_EQ(true, TypeParam::is_on_curve(a) && TypeParam::is_on_curve(b)); // rand is on curve
ASSERT_EQ(a + TypeParam::zero(), a); // zero addition
ASSERT_EQ(a + b - a, b); // addition,subtraction cancel
ASSERT_EQ(a + TypeParam::neg(a), TypeParam::zero()); // addition with neg cancel
ASSERT_EQ(a + a + a, scalar_t::from(3) * a); // scalar multiplication
ASSERT_EQ(scalar_t::from(3) * (a + b), scalar_t::from(3) * a + scalar_t::from(3) * b); // distributive
ASSERT_EQ(a + b, a + TypeParam::to_affine(b)); // mixed addition projective+affine
ASSERT_EQ(a - b, a - TypeParam::to_affine(b)); // mixed subtraction projective-affine
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}