From d037163e1a0e0d585d7307cfdf6de78ffaf33d32 Mon Sep 17 00:00:00 2001 From: Sergi Soler Arrufat Date: Wed, 27 May 2026 17:19:08 +0200 Subject: [PATCH] feat: add svp_apply_dft_to_dft --- spqlios/arithmetic/module_api.c | 1 + spqlios/arithmetic/scalar_vector_product.c | 35 ++++++++++++++++ spqlios/arithmetic/vec_znx_arithmetic.h | 7 ++++ .../arithmetic/vec_znx_arithmetic_private.h | 9 ++++ test/spqlios_svp_test.cpp | 42 +++++++++++++++++++ 5 files changed, 94 insertions(+) diff --git a/spqlios/arithmetic/module_api.c b/spqlios/arithmetic/module_api.c index 4b349a9..05631ec 100644 --- a/spqlios/arithmetic/module_api.c +++ b/spqlios/arithmetic/module_api.c @@ -43,6 +43,7 @@ static void fill_fft64_virtual_table(MODULE* module) { module->func.vec_znx_big_automorphism = fft64_vec_znx_big_automorphism; module->func.svp_prepare = fft64_svp_prepare_ref; module->func.svp_apply_dft = fft64_svp_apply_dft_ref; + module->func.svp_apply_dft_to_dft = fft64_svp_apply_dft_to_dft_ref; module->func.znx_small_single_product = fft64_znx_small_single_product; module->func.znx_small_single_product_tmp_bytes = fft64_znx_small_single_product_tmp_bytes; module->func.vmp_prepare_contiguous = fft64_vmp_prepare_contiguous_ref; diff --git a/spqlios/arithmetic/scalar_vector_product.c b/spqlios/arithmetic/scalar_vector_product.c index 859893e..110cded 100644 --- a/spqlios/arithmetic/scalar_vector_product.c +++ b/spqlios/arithmetic/scalar_vector_product.c @@ -38,6 +38,18 @@ EXPORT void svp_apply_dft(const MODULE* module, // N a, a_size, a_sl); } +// result = ppol * a_dft +EXPORT void svp_apply_dft_to_dft(const MODULE* module, // N + const VEC_ZNX_DFT* res, uint64_t res_size, // output + const SVP_PPOL* ppol, // prepared pol + const VEC_ZNX_DFT* a_dft, uint64_t a_size // a +) { + module->func.svp_apply_dft_to_dft(module, // N + res, + res_size, // output + ppol, // prepared pol + a_dft, a_size); +} // result = ppol * a EXPORT void fft64_svp_apply_dft_ref(const MODULE* module, // N const VEC_ZNX_DFT* res, uint64_t res_size, // output @@ -61,3 +73,26 @@ EXPORT void fft64_svp_apply_dft_ref(const MODULE* module, // then extend with zeros memset(dres + auto_end_idx * nn, 0, (res_size - auto_end_idx) * nn * sizeof(double)); } + +// result = ppol * a_dft +EXPORT void fft64_svp_apply_dft_to_dft_ref(const MODULE* module, // N + const VEC_ZNX_DFT* res, uint64_t res_size, // output + const SVP_PPOL* ppol, // prepared pol + const VEC_ZNX_DFT* a_dft, uint64_t a_size // a +) { + const uint64_t nn = module->nn; + double* const dres = (double*)res; + double* const dppol = (double*)ppol; + + const uint64_t auto_end_idx = res_size < a_size ? res_size : a_size; + for (uint64_t i = 0; i < auto_end_idx; ++i) { + double* const res_ptr = dres + i * nn; + // copy the polynomial to res, apply fft in place, call fftvec_mul in place. + double* vec_input = (double*)a_dft; + reim_fftvec_mul(module->mod.fft64.mul_fft, res_ptr, dppol, vec_input + i * nn); + ; + } + + // then extend with zeros + memset(dres + auto_end_idx * nn, 0, (res_size - auto_end_idx) * nn * sizeof(double)); +} diff --git a/spqlios/arithmetic/vec_znx_arithmetic.h b/spqlios/arithmetic/vec_znx_arithmetic.h index a247475..fde7df0 100644 --- a/spqlios/arithmetic/vec_znx_arithmetic.h +++ b/spqlios/arithmetic/vec_znx_arithmetic.h @@ -296,6 +296,13 @@ EXPORT void svp_apply_dft(const MODULE* module, // N const int64_t* a, uint64_t a_size, uint64_t a_sl // a ); +/** @brief apply a svp product, result = ppol * a_dft, presented in DFT space */ +EXPORT void svp_apply_dft_to_dft(const MODULE* module, // N + const VEC_ZNX_DFT* res, uint64_t res_size, // output + const SVP_PPOL* ppol, // prepared pol + const VEC_ZNX_DFT* a_dft, uint64_t a_size // a +); + /** @brief prepares a svp polynomial */ EXPORT void svp_prepare(const MODULE* module, // N SVP_PPOL* ppol, // output diff --git a/spqlios/arithmetic/vec_znx_arithmetic_private.h b/spqlios/arithmetic/vec_znx_arithmetic_private.h index a9f173d..9b8551c 100644 --- a/spqlios/arithmetic/vec_znx_arithmetic_private.h +++ b/spqlios/arithmetic/vec_znx_arithmetic_private.h @@ -81,6 +81,7 @@ typedef typeof(vec_znx_big_rotate) VEC_ZNX_BIG_ROTATE_F; typedef typeof(vec_znx_big_automorphism) VEC_ZNX_BIG_AUTOMORPHISM_F; typedef typeof(svp_prepare) SVP_PREPARE; typedef typeof(svp_apply_dft) SVP_APPLY_DFT_F; +typedef typeof(svp_apply_dft_to_dft) SVP_APPLY_DFT_TO_DFT_F; typedef typeof(znx_small_single_product) ZNX_SMALL_SINGLE_PRODUCT_F; typedef typeof(znx_small_single_product_tmp_bytes) ZNX_SMALL_SINGLE_PRODUCT_TMP_BYTES_F; typedef typeof(vmp_prepare_contiguous) VMP_PREPARE_CONTIGUOUS_F; @@ -130,6 +131,7 @@ struct module_virtual_functions_t { VEC_ZNX_BIG_AUTOMORPHISM_F* vec_znx_big_automorphism; SVP_PREPARE* svp_prepare; SVP_APPLY_DFT_F* svp_apply_dft; + SVP_APPLY_DFT_TO_DFT_F* svp_apply_dft_to_dft; ZNX_SMALL_SINGLE_PRODUCT_F* znx_small_single_product; ZNX_SMALL_SINGLE_PRODUCT_TMP_BYTES_F* znx_small_single_product_tmp_bytes; VMP_PREPARE_CONTIGUOUS_F* vmp_prepare_contiguous; @@ -290,6 +292,13 @@ EXPORT void fft64_svp_apply_dft_ref(const MODULE* module, const int64_t* a, uint64_t a_size, uint64_t a_sl // a ); +/** @brief apply a svp product, result = ppol * a_dft, presented in DFT space */ +EXPORT void fft64_svp_apply_dft_to_dft_ref(const MODULE* module, // N + const VEC_ZNX_DFT* res, uint64_t res_size, // output + const SVP_PPOL* ppol, // prepared pol + const VEC_ZNX_DFT* a_dft, uint64_t a_size // a +); + /** @brief sets res = k-normalize(a) -- output in int64 coeffs space */ EXPORT void fft64_vec_znx_big_normalize_base2k(const MODULE* module, // N uint64_t k, // base-2^k diff --git a/test/spqlios_svp_test.cpp b/test/spqlios_svp_test.cpp index c75f00e..ba7f16f 100644 --- a/test/spqlios_svp_test.cpp +++ b/test/spqlios_svp_test.cpp @@ -45,3 +45,45 @@ void test_fft64_svp_apply_dft(SVP_APPLY_DFT_F svp) { TEST(fft64_svp_apply_dft, svp_apply_dft) { test_fft64_svp_apply_dft(svp_apply_dft); } TEST(fft64_svp_apply_dft, fft64_svp_apply_dft_ref) { test_fft64_svp_apply_dft(fft64_svp_apply_dft_ref); } + +void test_fft64_svp_apply_dft_to_dft(SVP_APPLY_DFT_TO_DFT_F svp_dft) { + for (uint64_t n : {2, 4, 8, 64, 128}) { + MODULE* module = new_module_info(n, FFT64); + // poly 1 to multiply - create and prepare + fft64_svp_ppol_layout ppol(n); + ppol.fill_random(1.); + for (uint64_t sa : {3, 5, 8}) { + for (uint64_t sr : {3, 5, 8}) { + // poly 2 to multiply + fft64_vec_znx_dft_layout a_dft(n, sa); + a_dft.fill_dft_random_log2bound(19); + // original operation result + fft64_vec_znx_dft_layout res(n, sr); + thash hash_a_before = a_dft.content_hash(); + thash hash_ppol_before = ppol.content_hash(); + svp_dft(module, res.data, sr, ppol.data, a_dft.data, sa); + ASSERT_EQ(a_dft.content_hash(), hash_a_before); + ASSERT_EQ(ppol.content_hash(), hash_ppol_before); + // create expected value + reim_fft64vec ppo = ppol.get_copy(); + std::vector expect(sr); + for (uint64_t i = 0; i < sr; ++i) { + expect[i] = ppo * a_dft.get_copy_zext(i); + } + // this is the largest precision we can safely expect + double prec_expect = n * pow(2., 19 - 52); + for (uint64_t i = 0; i < sr; ++i) { + reim_fft64vec actual = res.get_copy_zext(i); + ASSERT_LE(infty_dist(actual, expect[i]), prec_expect); + } + } + } + + delete_module_info(module); + } +} + +TEST(fft64_svp_apply_dft_to_dft, svp_apply_dft_to_dft) { test_fft64_svp_apply_dft_to_dft(svp_apply_dft_to_dft); } +TEST(fft64_svp_apply_dft_to_dft, fft64_svp_apply_dft_to_dft_ref) { + test_fft64_svp_apply_dft_to_dft(fft64_svp_apply_dft_to_dft_ref); +}