同态加密和SEAL库的介绍(二)BFV 基础方案实现

写在前面:
        本篇具体讲解如何使用 BFV 加密方案对加密的整数进行简单的计算(一个多项式评估),来源是官方提供的示例。BFV 是比较常见的方案,在很多大模型推理的时候,都是将浮点数的权重和输入变换成整数后用 BFV 方案来实现。

一、参数的设置

        需要进行的第一个任务是设置一个 EncryptionParameters 类的实例。理解不同参数的行为、它们如何影响加密方案、性能以及安全级别是至关重要的。先指定方案模式:

using namespace std;
using namespace seal;EncryptionParameters parms(scheme_type::bfv);

需要设置三个加密参数:

  •  poly_modulus_degree(多项式模数的度数)
  •  coeff_modulus([密文]系数模数)
  •  plain_modulus(明文模数,仅适用于BFV方案)

        对加密后的密文进行操作时,只能使用特定的操作(加减法和乘法),尤其是乘法要关注其深度。因为每个密文都有一个特定的量,称为`不变噪声预算`(简称`噪声预算`),以比特为单位测量。新加密的密文中的噪声预算(初始噪声预算)由加密参数决定。同态运算以由加密参数决定的速率消耗噪声预算。

        在BFV中,允许对加密数据进行的两个基本操作是加法和乘法,其中相比于乘法,加法在噪声预算消耗方面几乎可以认为是免费的。由于噪声预算在连续乘法中会累积消耗,选择适当的加密参数的最重要因素是用户想要在加密数据上评估的算术电路的乘法深度。

        一旦密文的噪声预算达到零,它就会变得过于损坏而无法解密。因此,必须选择足够大的参数以支持所需的计算(过大会影响性能);否则,即使使用密钥也无法理解结果。

1.1 poly_modulus_degree

        第一个参数是`多项式模数`的度数。这必须是2的正幂,代表2幂次的循环多项式的度数;
较大的poly_modulus_degree会使密文尺寸变大且所有操作变慢,但能进行更复杂的加密计算。推荐的值是1024、2048、4096、8192、16384、32768,但也可以超出这个范围。

size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);

1.2 coeff_modulus

        接下来设置[密文]`系数模数`(coeff_modulus)。该参数是一个大整数,是不同素数的乘积,每个素数最多为60位。它表示为这些素数组成的向量,每个素数由Modulus类的一个实例表示。coeff_modulus 的位长度意味着其素数因子位长度的总和。

        较大的 coeff_modulus 意味着较大的噪声预算,因此有更多的加密计算能力。然而,coeff_modulus 的总位长度上限由 poly_modulus_degree 决定:

poly_modulus_degreemax coeff_modulus bit-length
102427
204854
4096109
8192218
16384438
32768881

        这些数字也可以在 native/src/seal/util/hestdparms.h 中找到,编码在函数SEAL_HE_STD_PARMS_128_TC 中,也可以通过函数CoeffModulus::MaxBitCount(poly_modulus_degree) 获得。

        例如,如果 poly_modulus_degree 为4096,coeff_modulus 可以由三个36位的素数组成(108位)。

        SEAL提供了选择coeff_modulus的辅助函数。对于新用户,最简单的方法是直接使用CoeffModulus::BFVDefault(poly_modulus_degree),它返回一个 std::vector<Modulus>,其中包含对于给定poly_modulus_degree 通常较好的选择。

parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));

1.3 plain_modulus

        明文模数可以是任何正整数,尽管这里我们将其设为2的幂。事实上,在很多情况下,可能希望它是一个素数;明文模数决定了明文数据类型的大小以及乘法中噪声预算的消耗。因此,为了获得最佳性能,必须尽量保持明文数据类型尽可能小。明文模数是BFV方案特有的,使用CKKS方案时不能设置。
        新加密密文中的噪声预算为:log2(coeff_modulus/plain_modulus)(比特)
        同态乘法中的噪声预算消耗形式为 log2(plain_modulus)  +(其他项)。

parms.set_plain_modulus(1024);

所有参数都设置好了,构建一个SEALContext对象,Microsoft SEAL将首先验证这些参数。

SEALContext context(parms);

官方例子中,打印了这个校验结果:

f41b38b04c0746608a7913c9c347a1ee.png

二、加解密

        Microsoft SEAL中的加密方案是公钥加密方案。这样,多个方可以使用同一个共享公钥加密数据,但只有数据的正确接收者才能使用密钥解密。

        生成密钥和公钥,我们需要一个 KeyGenerator 类的实例。构建一个 KeyGenerator 会自动生成一个密钥。然后,我们可以使用 KeyGenerator::create_public_key 为其创建任意多的公钥。注意,KeyGenerator::create_public_key 还有一个不带参数的重载,返回一个Serializable<PublicKey> 对象。

KeyGenerator keygen(context);
SecretKey secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
// 为了能够加密,需要构建Encryptor实例。
// Encryptor只需要公钥
Encryptor encryptor(context, public_key);

对密文的计算是通过Evaluator类进行的。在实际使用中,Evaluator不会由持有密钥的一方构建。

Evaluator evaluator(context);

解密以验证一切正常,所以我们还需要构建一个 Decryptor 实例。注意,Decryptor 需要密钥。

Decryptor decryptor(context, secret_key);

三、多项式运算

        示例中的运算是评估评4次多项式,输入是加密的x = 6,多项式的系数可以看作是明文输入:

   eq?4x%5E4%20&plus;%208x%5E3%20&plus;%208x%5E2%20&plus;%208x%20&plus;%204

        在BFV方案中,明文是度小于多项式模数的多项式,系数是模明文模数的整数。对于有环理论背景的读者来说,明文空间是多项式商环 Z_T[X]/(X^N + 1),其中N是 poly_modulus_degree,T 是 plain_modulus。

3.1 对输入进行编码和加密

        这里编码是说把输入的变量(当然向量也可以,下面在batch_encoder会介绍),编码成Plaintext 对象,然后再加密成 Ciphertext对象。这里用的构造函数将多项式作为字符串,系数表示为十六进制数

uint64_t x = 6;
Plaintext x_plain(uint64_to_hex_string(x));Ciphertext x_encrypted;
encryptor.encrypt(x_plain, x_encrypted);

        在Microsoft SEAL中,一个有效的密文由两个或更多个多项式组成,其系数是模coeff_modulus中素数乘积的整数。密文中的多项式数量称为它的“大小”,由 Ciphertext::size()给出,新加密的密文总是大小为2。
        这里可以直接打印查看其刚加密时的噪声预算:

cout << "    + noise budget in freshly encrypted x: " << decryptor.invariant_noise_budget(x_encrypted) << " bits" << endl;

示例这里打印结果是:

c30dc4c2604644c28a1b1185836e9d01.png

3.2 运算

        使用Microsoft SEAL时,通常有利于以最小化最长连续乘法链的方式进行计算。换句话说,最好的加密计算评估方式是最小化计算的乘法深度,因为总噪声预算消耗与乘法深度成正比。

        故这里对上面的多项式计算进行因式分解,以获得一个简单的深度2的表示是有利的。

eq?4x%5E4%20&plus;%208x%5E3%20&plus;%208x%5E2%20&plus;%208x%20&plus;%204%20%3D%204%28x%20&plus;%201%29%5E2%20*%20%28x%5E2%20&plus;%201%29

因此,我们分别计算  eq?%28x%20&plus;%201%29%5E2 和  eq?%28x%5E2%20&plus;%201%29 ,然后再乘以4即可。


首先,我们计算x^2并加上一个明文“1”:

Ciphertext x_sq_plus_one;
evaluator.square(x_encrypted, x_sq_plus_one);
Plaintext plain_one("1");
evaluator.add_plain_inplace(x_sq_plus_one, plain_one);

        加密乘法导致输出密文的大小增加。更确切地说,如果输入密文的大小为M和N,则同态乘法后的输出密文大小为 M+N-1。我们先察密文大小的增长和噪声预算的消耗:

cout << " + size of x_sq_plus_one: " << x_sq_plus_one.size() << endl;
cout << " + noise budget in x_sq_plus_one: " << decryptor.invariant_noise_budget(x_sq_plus_one) << " bits" << endl;

示例中输出如下(这里示例还进行了解密,加以验证中间结果,注意是十六进制):

d377c41901ad49d7a2a16a7c5b012ebf.png

        通过输出可以发现,乘法消耗了大量的噪声预算,并且增加了密文长度。但是解密可以发现,尽管大小增加了,只要噪声预算没有达到0,解密仍然可以正常工作


接下来,我们计算  eq?%28x%20&plus;%201%29%5E2

Ciphertext x_plus_one_sq;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one_sq);
evaluator.square_inplace(x_plus_one_sq);

        注意这里是用的 x_encrypted ,区分上面的中间结果 x_sq_plus_one,即噪声消耗也是针对初始的 55bit,这里对密文长度和噪声进行输出,并解密验证(注意是十六进制):

d2eb76f63f174ceb8c2a581fcbcafcd0.png


最后,我们计算  eq?%28x%5E2%20&plus;%201%29%20*%20%28x%20&plus;%201%29%5E2%20*%204 :

Ciphertext encrypted_result;
Plaintext plain_four("4");
evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);

        注:这里调用的两个乘法不一样,因为 "明文乘密文" 和 "密文乘密文" 是不一样的,前者时间更短,下面会具体讲这几个运算的函数。照上例对乘法结果的情况进行输出:

fbaaba96b1ed4e8caf21afbe7dcb2db4.png

        如果噪声预算达到0,这意味着解密无法得到正确的结果。这是因为密文 x_sq_plus_one 和 x_plus_one_sq 由于之前的平方运算都包含3个多项式,并且对大密文的同态操作比对小密文的计算消耗更多的噪声预算,对较小的密文进行计算在计算上也明显更便宜


3.3 重新线性化

        Relinearization - 重新线性化 是一种将乘法后的密文大小减少回初始大小2的操作。因此,在下一次乘法前对一个或两个输入密文进行重新线性化,即使重新线性化本身具有显著的计算成本,也可以对噪声增长和性能产生巨大的正面影响。重新线性化只能将大小3的密文减少到大小2,所以通常用户会在每次乘法后重新线性化以保持密文大小为2。
       
但是注意这里也只是在密文乘密文后,密文大小会增大才使用的重新线性化,故一般在明文乘密文后不用重新线性化。

        重新线性化需要特殊的`RelinKeys',可以将其视为一种公钥。重新线性化密钥可以通过KeyGenerator 轻松创建:

RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);

        重新线性化在 BFV 和 CKKS 方案中使用类似,但在这个例子中我们继续使用BFV。我们重复之前的计算,但这次在每次乘法后进行重新线性化:

Ciphertext x_squared;
evaluator.square(x_encrypted, x_squared);
evaluator.relinearize_inplace(x_squared, relin_keys);
evaluator.add_plain(x_squared, plain_one, x_sq_plus_one);Ciphertext x_plus_one;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one);
evaluator.square(x_plus_one, x_plus_one_sq);
evaluator.relinearize_inplace(x_plus_one_sq, relin_keys);evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);
evaluator.relinearize_inplace(encrypted_result, relin_keys);

像之前一样,打印中间结果可以看到:

8425b8c6900b40a4a7ad880a23e4e29b.png

        可以看出来,每次乘完的 重新线性化让多项式长度减少,避免了两个长度为三的密文相乘,改善了噪声的消耗。这样还有噪声剩余,可以进行其他进一步运算。
        当然,需要注意,如果像示例中这样,运算只进行到此,即没有更深的乘法深度,即噪声预算已经够用。但是重新线性化会带来额外的时间开销,故就不一定需要了,按情况而定。

3.4 结果解密

decryptor.decrypt(encrypted_result, decrypted_result);
cout << "    + decryption of 4(x^2+1)(x+1)^2 = 0x" << decrypted_result.to_string() << " ...... Correct." << endl;

打印如下:

634af6dd1b614029b86d6eca38fd2d5c.png

        但是需要注意一点,对于 x=64(x^2+1)(x+1)^2 = 7252 ,由于明文模数设置为1024,这个结果是在整数模 1024 下计算的。因此,预期输出应该是7252 % 1024 == 84,或者十六进制表示为 0x54。

        因此明文模数设置的小了,就不能还原计算结果;但是太大了,计算速度就比较慢,故最好计算之前进行一定的评估,设置合理的参数。


        有时我们创建的自定义加密参数会变得无效。比如这里我们简单地减少多项式模数度数,使参数不符合 HomomorphicEncryption.org 的安全标准,这些信息有助于修复无效的加密参数。

parms.set_poly_modulus_degree(2048);
context = SEALContext(parms);
print_parameters(context);
cout << "Parameter validation (failed): " << context.parameter_error_message();

3301098b9b524954916da29f908cd2bf.png

四、运算函数

        这里简单介绍上面所用到的几个运算的函数,方便大家更好的使用,这些都可以在 evaluator.h 里面找到。当然,大部分运算是不挑模式的,即 BFV 和 CKKS 都可以直接使用。因为内容过多,所以相似的部分就会省略。

        首先来看看内存, 可以打印我们从当前内存池中分配了多少内存。默认情况下,内存池将是一个静态全局池,可以使用MemoryManager类来更改它。 大多数用户几乎没有理由修改内存分配系统。

size_t megabytes = MemoryManager::GetPool().alloc_byte_count() >> 20;
cout << "[" << setw(7) << right << megabytes << " MB] "<< "Total allocation from the memory pool" << endl;

4.1 加法

 void add_inplace(Ciphertext &encrypted1, const Ciphertext &encrypted2)
  • 功能:这个函数用于将两个密文相加。它将 encrypted1encrypted2 相加,并将结果存储在 encrypted1 中。
  • 参数
    • encrypted1:第一个要相加的密文。
    • encrypted2:第二个要相加的密文。
  • 异常
    • std::invalid_argument:如果 encrypted1encrypted2 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 处于不同的NTT形式(Number Theoretic Transform形式),则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 处于不同的级别或尺度,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void add(const Ciphertext &encrypted1, const Ciphertext &encrypted2, Ciphertext &destination)
  • 功能:这个函数用于将两个密文相加。它将 encrypted1encrypted2 相加,并将结果存储在 destination 参数中。
  • 参数
    • encrypted1:第一个要相加的密文。
    • encrypted2:第二个要相加的密文。
    • destination:要用加法结果覆盖的密文。
void add_many(const std::vector<Ciphertext> &encrypteds, Ciphertext &destination) const;
  • 功能:这个函数用于将一个密文向量中的所有密文相加,并将结果存储在 destination 参数中。
  • 参数
    • encrypteds:要相加的密文向量。
    • destination:要用加法结果覆盖的密文。
  • 异常
    • std::invalid_argument:如果 encrypteds 为空,则抛出此异常。
    • std::invalid_argument:如果 encrypteds 中的密文对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypteds 中的密文处于不同的NTT形式,则抛出此异常。
    • std::invalid_argument:如果 encrypteds 中的密文处于不同的级别或尺度,则抛出此异常。
    • std::invalid_argument:如果 destinationencrypteds 中的一个密文,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。

同理还有加明文的:

void add_plain_inplace(Ciphertext &encrypted, const Plaintext &plain, MemoryPoolHandle pool = MemoryManager::GetPool()) const;
inline void add_plain(const Ciphertext &encrypted, const Plaintext &plain, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const

4.2 减法

void sub_inplace(Ciphertext &encrypted1, const Ciphertext &encrypted2) const;
  • 功能:这个函数用于将两个密文相减。它计算 encrypted1encrypted2 的差,并将结果存储在 encrypted1 中。
  • 参数
    • encrypted1:要被减的密文。
    • encrypted2:要减去的密文。
  • 异常
    • std::invalid_argument:如果 encrypted1encrypted2 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 处于不同的NTT形式,则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 处于不同的级别或尺度,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void sub(const Ciphertext &encrypted1, const Ciphertext &encrypted2, Ciphertext &destination) const
  • 功能:这个函数用于将两个密文相减。它计算 encrypted1encrypted2 的差,并将结果存储在 destination 参数中。
  • 参数
    • encrypted1:要被减的密文。
    • encrypted2:要减去的密文。
    • destination:要用减法结果覆盖的密文。
void sub_plain_inplace(Ciphertext &encrypted, const Plaintext &plain, MemoryPoolHandle pool = MemoryManager::GetPool()) const;
inline void sub_plain(const Ciphertext &encrypted, const Plaintext &plain, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const

4.3 乘法

void multiply_inplace(Ciphertext &encrypted1, const Ciphertext &encrypted2,MemoryPoolHandle pool = MemoryManager::GetPool()) const;
  • 功能:这个函数用于将两个密文相乘。它计算 encrypted1encrypted2 的乘积,并将结果存储在 encrypted1 中。在这个过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。
  • 参数
    • encrypted1:第一个要相乘的密文。
    • encrypted2:第二个要相乘的密文。
    • pool:指向有效内存池的 MemoryPoolHandle。(一般忽略)
  • 异常
    • std::invalid_argument:如果 encrypted1encrypted2 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 不处于默认的NTT形式,则抛出此异常。
    • std::invalid_argument:如果 encrypted1encrypted2 处于不同的级别,则抛出此异常。
    • std::invalid_argument:如果输出尺度对加密参数来说太大,则抛出此异常。
    • std::invalid_argument:如果 pool 未初始化,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void multiply(const Ciphertext &encrypted1, const Ciphertext &encrypted2, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const
  • 功能:这个函数用于将两个密文相乘。它计算 encrypted1encrypted2 的乘积,并将结果存储在 destination 参数中。在这个过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。
  • 参数
    • encrypted1:第一个要相乘的密文。
    • encrypted2:第二个要相乘的密文。
    • destination:要用乘法结果覆盖的密文。
    • pool:指向有效内存池的 MemoryPoolHandle
 void multiply_plain_inplace(Ciphertext &encrypted, const Plaintext &plain, MemoryPoolHandle pool = MemoryManager::GetPool()) const;
 inline void multiply_plain(const Ciphertext &encrypted, const Plaintext &plain, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const

4.4 平方

void square_inplace(Ciphertext &encrypted, MemoryPoolHandle pool = MemoryManager::GetPool()) const;
  • 功能:这个函数用于对一个密文进行平方运算。它计算 encrypted 的平方。在这个过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。
  • 参数
    • encrypted:要进行平方运算的密文。
    • pool:指向有效内存池的 MemoryPoolHandle
  • 异常
    • std::invalid_argument:如果 encrypted 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted 不处于默认的NTT形式,则抛出此异常。
    • std::invalid_argument:如果输出尺度对加密参数来说太大,则抛出此异常。
    • std::invalid_argument:如果 pool 未初始化,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void square(const Ciphertext &encrypted, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const
  • 功能:这个函数用于对一个密文进行平方运算。它计算 encrypted 的平方,并将结果存储在 destination 参数中。在这个过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。
  • 参数
    • encrypted:要进行平方运算的密文。
    • destination:要用平方结果覆盖的密文。

4.5 重新线性化

inline void relinearize_inplace(Ciphertext &encrypted, const RelinKeys &relin_keys, MemoryPoolHandle pool = MemoryManager::GetPool()) const
  • 功能:这个函数用于对一个密文进行重线性化操作,减少其大小至2。如果密文的大小为K+1,则给定的重线性化密钥的大小至少需要为K-1。在这个过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。
  • 参数
    • encrypted:要进行重线性化的密文。
    • relin_keys:重线性化密钥。
    • pool:指向有效内存池的 MemoryPoolHandle
  • 异常
    • std::invalid_argument:如果 encryptedrelin_keys 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted 不处于默认的NTT形式,则抛出此异常。
    • std::invalid_argument:如果 relin_keys 不对应当前上下文中的顶级参数,则抛出此异常。
    • std::invalid_argument:如果 relin_keys 的大小太小,则抛出此异常。
    • std::invalid_argument:如果 pool 未初始化,则抛出此异常。
    • std::logic_error:如果上下文不支持密钥切换,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void relinearize(const Ciphertext &encrypted, const RelinKeys &relin_keys, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const;

4.6 幂运算

void exponentiate_inplace(Ciphertext &encrypted, std::uint64_t exponent, const RelinKeys &relin_keys,MemoryPoolHandle pool = MemoryManager::GetPool()) const;
  • 功能:这个函数用于对一个密文进行幂运算,将 encrypted 提升到 exponent 次幂。在计算过程中,动态内存分配从给定的 MemoryPoolHandle 指向的内存池中分配。幂运算以深度优化的顺序完成,并在每次乘法后自动进行重线性化,使用给定的重线性化密钥。
  • 参数
    • encrypted:要进行幂运算的密文。
    • exponent:密文要提升的幂次。
    • relin_keys:重线性化密钥。
    • pool:指向有效内存池的 MemoryPoolHandle
  • 异常
    • std::logic_error:如果加密方案不是 scheme_type::bfvscheme_type::bgv,则抛出此异常。
    • std::invalid_argument:如果 encryptedrelin_keys 对加密参数无效,则抛出此异常。
    • std::invalid_argument:如果 encrypted 不处于默认的NTT形式,则抛出此异常。
    • std::invalid_argument:如果输出尺度对加密参数来说太大,则抛出此异常。
    • std::invalid_argument:如果 exponent 为零,则抛出此异常。
    • std::invalid_argument:如果 relin_keys 的大小太小,则抛出此异常。
    • std::invalid_argument:如果 pool 未初始化,则抛出此异常。
    • std::logic_error:如果上下文不支持密钥切换,则抛出此异常。
    • std::logic_error:如果结果密文是透明的,则抛出此异常。
inline void exponentiate(const Ciphertext &encrypted, std::uint64_t exponent, const RelinKeys &relin_keys, Ciphertext &destination,MemoryPoolHandle pool = MemoryManager::GetPool()) const

4.7 其余

还有一部分因为此处没涉及到,所以就不展开介绍了,后面解释到对应部分会再补充。包括了:

  1. rotate_rows_inplace
  2. rotate_columns_inplace
  3. mod_switch_to_inplace
  4. rescale_to_inplace
  5. mod_reduce_to_inplace

五、结语

        在上面的例子中,我们仅使用了明文多项式的一个系数。这实际上是非常浪费的,因为明文多项式很大,而且无论如何都会被整体加密。同时,因为是取模故会产生溢出,如果直接增加 plain_modulus 参数,直到没有溢出发生,并且计算表现得像整数运算。问题在于增加 plain_modulus 会增加噪声预算的消耗,并且还会减少初始噪声预算。

        在下篇将讨论其他将数据布局到明文元素(编码)的方法,这些方法可以允许更多的计算而不会发生数据类型溢出,并且可以充分利用整个明文多项式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395477.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB学习笔记(三)

使用Python操作MongoDB: 使用管理员用户&#xff1a;

web基础与http协议与配置

目录 一、web基础 1.1 DNS与域名&#xff08;详解看前面章节&#xff09; 1.2 网页的概念&#xff08;HTTP/HTTPS&#xff09; 1.2.1 基本概念 1.2.2 HTML文档结构(了解) 1.2.3 web相关重点 1.2.4 静态资源和动态资源 二、http协议 2.1 概述 2.2 cookie和session&…

云原生真机实验

基于Proxmox VE构建中小企业云计算平台 首先Proxmox VE是什么&#xff1f;能用来做什么&#xff1f; Proxmox VE是一个完整的企业虚拟化开源平台。借助内置的 Web 界面&#xff0c;可以在单个解决方案上轻松管理 VM(开虚拟机的) 和容器、软件定义的存储和网络、高可用性群集以…

STM32开发之移植FreeRtos

一、新建STM32工程项目 &#xff08;1&#xff09;打开keil新建工程文件夹 &#xff08;2&#xff09;选择芯片型号 接下来会弹出来一个新建工程的小助手&#xff0c;我们关闭就好&#xff0c;接下来我们的工程就创建好了&#xff0c;但是工程还是空的 二、添加STM32的相关固件…

搭建 Web 群集Haproxy

案例概述 Haproxy 是目前比较流行的一种群集调度工具&#xff0c;同类群集调度工具有很多&#xff0c;如 LVS 和Nginx。相比较而言&#xff0c;LVS 性能最好&#xff0c;但是搭建相对复杂;Nginx 的upstream模块支持群集功能&#xff0c;但是对群集节点健康检查功能不强&#xf…

【C++】模版详解

1、概念 C模版分两类&#xff1a;函数模版和类模版 1&#xff09;函数模板的格式 template <class 形参名&#xff0c;class 形参名&#xff0c;......> 返回类型 函数名(参数列表) {函数体 }例如&#xff1a; template <class T> void swap(T& a, T& b…

机器人主板维修|ABB机械手主板元器件故障

【ABB机器人电路板故障原因诊断】 针对上述故障现象&#xff0c;我们需要对ABB机器人IO板进行详细的故障诊断。以下是一些可能的故障原因&#xff1a; 1. 元器件老化或损坏&#xff1a;ABB机械手安全面板上的元器件在长期使用过程中可能出现老化、损坏或接触不良等问题&#xf…

Unity 使用字符串更改Text指定文字颜色、大小、换行、透明

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用字符串改变文字属性的方法&#xff08;一&#xff09;修改颜色&#xff08;二&#xff09;修改大小&#xff08;三&#xff09;换行&#xff08;四&…

NC 矩阵的最小路径和

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个 n *…

【漏洞复现】某赛通数据泄露防护(DLP)系统 NetSecConfigAjax SQL注入漏洞

0x01 产品简介 某赛通新一代数据泄露防护系统&#xff08;简称 DLP&#xff09;&#xff0c;以服务企事业单位进行数据资产梳理、数据安全防护为目标。系统采用平台化管理&#xff0c;将终端DLP、网络DLP、邮件DLP、存储扫描DLP、API 接口DLP 进行统一管理&#xff0c;模块化控…

LVS详解

目录 一、LVS简介 LVS 官网: 二、LVS 负载均衡模式 2.1 LVS-NAT模式&#xff1a; 2.1.1 简介 2.1.2 工作流程图&#xff1a; 2.1.3 说明&#xff1a; 2.1.4 LVS-NAT的优缺点&#xff1a; 2.2 LVS-DR模式&#xff1a; 2.2.1 简介 2.2.2 工作原理&#xff1a; 2.2.3 工作…

Android----Depth Anything尝鲜 小米手机部署

题目要求&#xff1a;了解Depth Anything (以及Depth Anything v2)基本原理&#xff0c;创新点。 Depth Anything 论文&#xff1a;Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data 参考代码&#xff1a;Depth-Anything-Android GitHub 分析&#xff1a; …

应急响应:D盾的简单使用.

什么是应急响应. 一个组织为了 应对 各种网络安全 意外事件 的发生 所做的准备 以及在 事件发生后 所采取的措施 。说白了就是别人攻击你了&#xff0c;你怎么把这个攻击还原&#xff0c;看看别人是怎么攻击的&#xff0c;然后你如何去处理&#xff0c;这就是应急响应。 D盾功…

【算法】最短路径算法思路小结

一、基础&#xff1a;二叉树的遍历->图的遍历 提到搜索算法&#xff0c;就不得不说两个最基础的思想&#xff1a; BFS&#xff08;Breadth First Search&#xff09;广度优先搜索 DFS&#xff08;Depth First Search&#xff09;深度优先搜索 刚开始是在二叉树遍历中接触这…

【vue+mathjax】mathjax的使用

方法一、引用外网的地址 第一步&#xff1a;在public/index.html中引入地址 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta http-equiv"X-UA-Compatible" content"IEedge,chrome1" /><metaname…

力扣刷题-环形链表判断是否有环

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 首先&#xff0c;我们先来看一下这段代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ bool …

延时队列与redis and rabbitmq

延时队列是什么 延时队列&#xff08;Delay Queue&#xff09;是一种特殊的消息队列&#xff0c;它允许你在添加消息时设置一个延时时间&#xff0c;消息只有在延时时间到达后才能被消费。这种机制在分布式系统中非常有用&#xff0c;常用于处理需要在指定时间后执行的任务&am…

力扣面试经典算法150题:多数元素

多数元素 今天的题目是力扣面试经典150题中的数组的简单题: 多数元素 题目链接&#xff1a;https://leetcode.cn/problems/majority-element/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个大小为 n 的数组 nums&#xff0c;其中包含 n 个…

算法 二

求中点 LR&#xff0c;可能溢出 除以2&#xff0c;等同于右移一位 递归、递归的时间复杂度 母问题的规模 子问题的规模&#xff0c;且都相等 调用次数 不用展开看&#xff0c;就看一层。 归并排序 时间复杂度降低的原因&#xff1a;没有浪费比较。比如选择排序&#xff…

财务会计与管理会计(一)

文章目录 销售业绩统计图表OFFSET函数在制作图表数据中的应用 自动计算分项合计1、IF函数2、SUM函数3、SUMPRODUCT函数 自动打印快递邮寄单OFFSET函数在逐行获取数据中的应用 销售业绩统计图表 OFFSET函数在制作图表数据中的应用 B150FFSET($A$2,$M$1,COLUMN(B1)-1) B150FFSE…