diff --git a/native/src/seal/c/decryptor.cpp b/native/src/seal/c/decryptor.cpp index 18ec84842..014d21046 100644 --- a/native/src/seal/c/decryptor.cpp +++ b/native/src/seal/c/decryptor.cpp @@ -61,6 +61,26 @@ SEAL_C_FUNC Decryptor_Decrypt(void *thisptr, void *encrypted, void *destination) } } +SEAL_C_FUNC Decryptor_InvariantNoise(void *thisptr, void *encrypted, double *invariant_noise_budget) +{ + Decryptor *decryptor = FromVoid(thisptr); + IfNullRet(decryptor, E_POINTER); + Ciphertext *encryptedptr = FromVoid(encrypted); + IfNullRet(encryptedptr, E_POINTER); + IfNullRet(invariant_noise_budget, E_POINTER); + + try + { + *invariant_noise_budget = decryptor->invariant_noise(*encryptedptr); + return S_OK; + } + catch (const invalid_argument &) + { + return E_INVALIDARG; + } +} + + SEAL_C_FUNC Decryptor_InvariantNoiseBudget(void *thisptr, void *encrypted, int *invariant_noise_budget) { Decryptor *decryptor = FromVoid(thisptr); diff --git a/native/src/seal/c/decryptor.h b/native/src/seal/c/decryptor.h index 7c5dc747a..c26337569 100644 --- a/native/src/seal/c/decryptor.h +++ b/native/src/seal/c/decryptor.h @@ -19,4 +19,6 @@ SEAL_C_FUNC Decryptor_Destroy(void *thisptr); SEAL_C_FUNC Decryptor_Decrypt(void *thisptr, void *encrypted, void *destination); +SEAL_C_FUNC Decryptor_InvariantNoise(void *thisptr, void *encrypted, double *invariant_noise_budget); + SEAL_C_FUNC Decryptor_InvariantNoiseBudget(void *thisptr, void *encrypted, int *invariant_noise_budget); diff --git a/native/src/seal/decryptor.cpp b/native/src/seal/decryptor.cpp index c4a77faf9..06ef50d67 100644 --- a/native/src/seal/decryptor.cpp +++ b/native/src/seal/decryptor.cpp @@ -10,6 +10,7 @@ #include "seal/util/uintarith.h" #include "seal/util/uintcore.h" #include +#include #include using namespace std; @@ -377,8 +378,7 @@ namespace seal } } - int Decryptor::invariant_noise_budget(const Ciphertext &encrypted) - { + util::Pointer Decryptor::invariant_noise_internal(const Ciphertext &encrypted) { // Verify that encrypted is valid. if (!is_valid_for(encrypted, context_)) { @@ -438,6 +438,43 @@ namespace seal StrideIter wide_noise_poly((*noise_poly).ptr(), coeff_modulus_size); poly_infty_norm_coeffmod(wide_noise_poly, coeff_count, context_data.total_coeff_modulus(), norm.get(), pool_); + return norm; + } + + double Decryptor::invariant_noise(const Ciphertext &encrypted) { + double invariant_noise = 0.0; + + auto &context_data = *context_.get_context_data(encrypted.parms_id()); + auto &parms = context_data.parms(); + auto &coeff_modulus = parms.coeff_modulus(); + size_t coeff_modulus_size = coeff_modulus.size(); + + auto norm = invariant_noise_internal(encrypted); + + for (size_t i = 0; i < coeff_modulus_size; i++) { + auto power = static_cast(sizeof(uint64_t) * 8 * i); + auto word = static_cast(norm.get()[i]); + invariant_noise += word * exp2(power); + } + + double total_coeff = 1.0; + + for (auto coeff_mod : coeff_modulus) { + total_coeff *= static_cast(coeff_mod.value()); + } + + return invariant_noise / total_coeff; + } + + int Decryptor::invariant_noise_budget(const Ciphertext &encrypted) + { + auto norm = invariant_noise_internal(encrypted); + + auto &context_data = *context_.get_context_data(encrypted.parms_id()); + auto &parms = context_data.parms(); + auto &coeff_modulus = parms.coeff_modulus(); + size_t coeff_modulus_size = coeff_modulus.size(); + // The -1 accounts for scaling the invariant noise by 2; // note that we already took plain_modulus into account in compose // so no need to subtract log(plain_modulus) from this diff --git a/native/src/seal/decryptor.h b/native/src/seal/decryptor.h index 302a812f6..b1f14f644 100644 --- a/native/src/seal/decryptor.h +++ b/native/src/seal/decryptor.h @@ -73,6 +73,27 @@ namespace seal */ void decrypt(const Ciphertext &encrypted, Plaintext &destination); + /* + Computes the invariant noise of a ciphertext. The invariant noise is + a value that increases with FHE operations. This function only works + with the BFV scheme. + + @par Invariant Noise + The invariant noise polynomial of a ciphertext is a rational coefficient + polynomial, such that a ciphertext decrypts correctly as long as the + coefficients of the invariant noise polynomial are of absolute value less + than 1/2. Thus, we call the infinity-norm of the invariant noise polynomial + the invariant noise, and for correct decryption require it to be less than + 1/2. + + @param[in] encrypted The ciphertext + @throws std::invalid_argument if the scheme is not BFV + @throws std::invalid_argument if encrypted is not valid for the encryption + parameters + @throws std::invalid_argument if encrypted is in NTT form + */ + SEAL_NODISCARD double invariant_noise(const Ciphertext &encrypted); + /* Computes the invariant noise budget (in bits) of a ciphertext. The invariant noise budget measures the amount of room there is for the noise @@ -82,9 +103,9 @@ namespace seal @par Invariant Noise Budget The invariant noise polynomial of a ciphertext is a rational coefficient polynomial, such that a ciphertext decrypts correctly as long as the - coefficients of the invariantnoise polynomial are of absolute value less + coefficients of the invariant noise polynomial are of absolute value less than 1/2. Thus, we call the infinity-norm of the invariant noise polynomial - the invariant noise, and for correct decryption requireit to be less than + the invariant noise, and for correct decryption require it to be less than 1/2. If v denotes the invariant noise, we define the invariant noise budget as -log2(2v). Thus, the invariant noise budget starts from some initial value, which depends on the encryption parameters, and decreases when @@ -121,6 +142,8 @@ namespace seal // destination has the size of an RNS polynomial. void dot_product_ct_sk_array(const Ciphertext &encrypted, util::RNSIter destination, MemoryPoolHandle pool); + util::Pointer invariant_noise_internal(const Ciphertext &encrypted); + // We use a fresh memory pool with `clear_on_destruction' enabled. MemoryPoolHandle pool_ = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true); diff --git a/native/tests/seal/CMakeLists.txt b/native/tests/seal/CMakeLists.txt index c01a442bd..d93b50dae 100644 --- a/native/tests/seal/CMakeLists.txt +++ b/native/tests/seal/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(sealtest ${CMAKE_CURRENT_LIST_DIR}/ciphertext.cpp ${CMAKE_CURRENT_LIST_DIR}/ckks.cpp ${CMAKE_CURRENT_LIST_DIR}/context.cpp + ${CMAKE_CURRENT_LIST_DIR}/decryptor.cpp ${CMAKE_CURRENT_LIST_DIR}/encryptionparams.cpp ${CMAKE_CURRENT_LIST_DIR}/encryptor.cpp ${CMAKE_CURRENT_LIST_DIR}/evaluator.cpp diff --git a/native/tests/seal/decryptor.cpp b/native/tests/seal/decryptor.cpp new file mode 100644 index 000000000..d7e040572 --- /dev/null +++ b/native/tests/seal/decryptor.cpp @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +#include "seal/batchencoder.h" +#include "seal/context.h" +#include "seal/decryptor.h" +#include "seal/encryptor.h" +#include "seal/keygenerator.h" +#include "seal/modulus.h" +#include +#include +#include +#include +#include "gtest/gtest.h" + +using namespace seal; +using namespace std; + +namespace sealtest +{ + TEST(DecryptorTest, InvariantNoiseAndBudget) + { + EncryptionParameters parms(scheme_type::bgv); + Modulus plain_modulus(1 << 6); + parms.set_plain_modulus(plain_modulus); + parms.set_poly_modulus_degree(64); + parms.set_coeff_modulus(CoeffModulus::Create(64, { 60, 60, 60 })); + SEALContext context(parms, true, sec_level_type::none); + KeyGenerator keygen(context); + PublicKey pk; + keygen.create_public_key(pk); + + Encryptor encryptor(context, pk, keygen.secret_key()); + Decryptor decryptor(context, keygen.secret_key()); + + Ciphertext ct; + + encryptor.encrypt_zero(ct); + auto invariant_noise = decryptor.invariant_noise(ct); + auto invariant_noise_budget = decryptor.invariant_noise_budget(ct); + + auto calculated_noise_budget = floor(-log2(2. * invariant_noise)); + + ASSERT_DOUBLE_EQ(calculated_noise_budget, static_cast(invariant_noise_budget)); + } +} \ No newline at end of file