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

news2024/12/26 2:44:07

写在前面:
        本篇具体讲解如何使用 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.coloradmin.cn/o/1996095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新品周销量20W+,月GMV1300W+,黑马品牌如何实现快速突围?

随着视频号用户的不断增加&#xff0c;直播带货生态的不断发展&#xff0c;越来越多的品牌也开始入局视频号。 近期友望数据发现&#xff0c;不少新品牌在视频号上脱颖而出。比如服饰内衣行业品牌「瑰菲女神」&#xff0c;专注女性内衣裤行业&#xff0c;周销量近20W件&#xf…

Java 并发(二)—— AQS原理

AQS&#xff0c;全名AbstractQueuedSynchronizer。 抽象队列同步器定义多线程访问共享资源的同步模板&#xff0c;解决了实现自定义同步器时涉及的大量细节问题&#xff0c;简化开发两种同步状态&#xff1a;独占、共享核心组件&#xff1a;State变量、CLH变体队列、获取 / 释…

Leetcode每日刷题之75. 颜色分类(C++)

有接触过数据结构的同学应该知道排序有很多种类&#xff0c;我之前也出过一篇 排序大杂烩 的博客&#xff0c;其中包含了一部分排序的讲解&#xff0c;排序在我们学习编程的过程中有着至关重要的作用&#xff0c;不论是大部分新手刚开始接触的冒泡排序还是C库中的sort函数&…

对象类作为类成员(详解版)

我们在第13行创建了一个人的类&#xff0c;并且给它赋予了两个属性&#xff0c;一个为int 类型的age,另一个则是Phone类型的 name,众所周知class类是一个自定义的数据类型&#xff0c;和struct相似&#xff0c;两者在默认情况下的权限不一样。而我们这时候没有Phone的数据类型&…

Docker 入门全攻略:安装、操作与常用命令指南

目录 Docker 入门全攻略&#xff1a;安装、操作与常用命令指南 一、引言 二、Docker 下载与安装 2.1 Docker 的系统要求 2.2 安装步骤 ①对于 Windows 的安装指南 ②对于 macOS 的安装指南 ③对于 Linux 的安装指南 三、Docker 的基本概念 3.1 镜像&#xff08;Image…

用Python实现特征工程之特征变换——数值特征的归一化和标准化、类别特征的编码、特征组合和分解、特征缩放

特征工程中的特征变换是一个重要的步骤&#xff0c;旨在通过转换原始特征来提高模型的性能。特征变换主要包括数值特征的归一化和标准化、类别特征的编码、特征组合和分解、以及特征缩放等。下面将详细讲解这些内容&#xff0c;并提供相应的Python代码示例。 1. 数值特征的归一…

为什么要选择开放式耳机?开放式耳机的优缺点

今天咱们来聊聊一个特别有意思的话题&#xff1a;为什么我们要选择开放式耳机&#xff1f;在这个耳机种类繁多的世界里&#xff0c;开放式耳机以其独特的魅力&#xff0c;赢得了不少音乐爱好者和运动达人的青睐。 开放式耳机最大的特点就是它不会完全封闭你的耳朵&#xff0c;这…

C语言之“ 分支和循环 ” (2)

&#x1f339;个人主页&#x1f339;&#xff1a;喜欢草莓熊的bear &#x1f339;专栏&#x1f339;&#xff1a;C语言基础 目录 前言 一、switch语句 1.1 if语句和switch语句的对比 1.2 switch语句中的break 1.3 switch语句中的default 1.4 switch语句中的case和default…

信息系统运维管理:深度解析与方案建议

在现代企业环境中&#xff0c;信息系统运维管理扮演着至关重要的角色。它不仅是信息系统高效、安全和稳定运行的保障&#xff0c;更是企业业务发展的坚实支撑。本文将深入探讨服务设计阶段、服务转换阶段、委托系统维护管理这三个关键阶段&#xff0c;并结合最新的互联网技术和…

Transformer模型注意力机制;层归一化(Layer Normalization)

目录 Transformer模型注意力机制 注意力机制(Attention Mechanism) 层归一化(Layer Normalization) 层归一化的作用 Transformer模型注意力机制 中,有几个关键的公式和概念需要解释,包括注意力机制(Attention Mechanism)和层归一化(Layer Normalization)。以下是…

halcon不良黄豆数量检测和大米视觉处理winform

winform halcon不良黄豆数量检测 不良黄豆数量检测.hdev 首先检测黄豆的数量 然后检测不良黄豆的数量 在winform中对其进行分开显示处理 大米视觉处理类似 这段代码主要用于图像处理和显示&#xff0c;使用的是Halcon语言。让我们逐步解释每个部分的功能&#xff1a; 构造函数…

Adnroid 数据存储:SharedPreferences详解【SharedPreferencesUtils,SharedPreferences的ANR】

目录 1&#xff09;SP是什么、如何使用&#xff0c;SPUtils 2&#xff09;SP的流程 3&#xff09;comit和apply 一、SP是什么&#xff0c;如何使用&#xff0c;SPUtils 1.1 SP是什么&#xff1f; SharedPreferences是Android平台提供的一种轻量级的数据存储方式&#xff0c;…

【Linux SQLite数据库】一、SQLite交叉编译与移植

SQLite 是一个用 C 语言编写的开源、轻量级、快速、独立且高可靠性的 SQL 数据库引擎&#xff0c;它提供了功能齐全的数据库解决方案。SQLite 几乎可以在所有的手机和计算机上运行&#xff0c;它被嵌入到无数人每天都在使用的众多应用程序中。此外&#xff0c;SQLite 还具有稳定…

git push上不去的问题Iremote reiectedl——文件过大的问题

在新建分支的时候&#xff0c;发现push怎么也上传不上去&#xff0c;一开始觉得是权限的问题&#xff0c;但是尝试了各种方案都没有用&#xff0c;后面再仔细看了一下是文件太大了&#xff0c;远程拒绝推送 接下来&#xff0c;和大家讲讲我的解决方案 1、把修改的代码迁移到新…

将QT工程打包生成可单独运行的.exe程序

将QT工程打包生成可单独运行的.exe程序 1、生成exe文件2、导入qt配置文件3、打包qt配置文件和exe文件4、单个可执行exe文件 1、生成exe文件 Qt中执行Release操作&#xff0c;生成对应的exe文件。 Release的路径下会生成如下文件&#xff1a; 进入release文件夹&#xff0c;双…

【Linux基础】Linux基本指令(二)

目录 &#x1f680;前言一&#xff0c;mv指令二&#xff0c;more & less指令2.1 more 指令2.1 less指令 三&#xff0c;重定向技术(重要)3.1 echo指令3.2 输出重定向 >3.3 追加重定向 >>3.4 输入重定向 < 四&#xff0c;head & tail指令4.1 head 指令4.2 t…

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

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

PLL基本原理、设计及应用

PLL基本原理 锁相环&#xff08;Phase-Locked Loop, PLL&#xff09;是一种基本的反馈控制系统&#xff0c;广泛应用于电子通信、信号处理、时钟同步等多个领域。PLL通过反馈机制锁定输入信号的频率和相位&#xff0c;从而实现输出信号与输入信号的同步。其基本工作原理可以概…

Linux知识复习第2期

RHCE 远程登录服务-CSDN博客 Linux 用户和组管理_linux用户和组的管理-CSDN博客 Linux 文件权限详解-CSDN博客 目录 1、sshd 免密登录 (1)纯净实验环境 (2)生成密钥 (3)上锁 2、用户管理 (1)添加新用户 (2)删除用户 (3)修改用户信息 (4)为用户账号设…

vue路由学习

1、基本了解 &#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09;在创建vue项目时&#xff0c;就已经勾选了vue-router 2、 &#xff08;0&#xff09;自己手写了一个新的组件文件(部门管理)&#xff08;DeptView.vue&#xff09; &#xff08;1&a…