SEAL全同态加密开源库(十四) encoders-源码浅析

2021SC@SDUSC

2021-12-26

前言

前几篇我们讨论了SEAL对于BFV、CKKS的实现。本篇开始我将开始讨论SEAL中encoders.cpp代码实现。

背景

在“1 _bfv_basics。我们展示了如何使用BFV方案执行一个非常简单的计算。计算以明文模为参数,仅利用一个BFV明文多项式的系数。这种方法有两个值得注意的问题:

(1)实际应用中一般采用整数或实数算法,而不是模运算;
(2)我们只使用了明文多项式的一个系数。这是非常浪费的,因为明文多项式很大,而且在任何情况下都将全部加密。

对于(1),有人可能会问,为什么不直接增加plain_modules参数,直到没有溢出发生,并且计算的行为就像整数算术一样。问题是增加plain_module会增加噪声预算消耗,同时降低初始噪声预算。

在这些示例中,我们将讨论将数据放入明文元素(编码)的其他方法,这些方法允许进行更多的计算,而不会出现数据类型溢出,并且允许使用完整的明文多项式。

源码分析

void example_batch_encoder()

void example_batch_encoder()
{
    
    
    print_example_banner("Example: Encoders / Batch Encoder");

设N为poly_modulus_degree,T为plain_modulus。批处理允许BFV明文多项式被视为2×(N/2)矩阵,每个元素都是一个模T整数。在矩阵视图中,加密操作在加密矩阵上逐个执行element-wise,允许用户在完全向量化计算中获得几个数量级的速度提升。因此,除了最简单的计算之外,批处理应该是首选的方法。使用BFV时,如果使用得当,实现的性能将超过使用IntegerEncoder完成的任何工作。

EncryptionParameters parms(scheme_type::BFV);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));

为了启用批处理,我们需要将plain_modules设置为一个素数,它全等于1模2*poly_modulus_degree。(这里是指要求素数满足的条件)。Microsoft SEAL提供了一种辅助方法来查找这样的素数。在本例中,我们创建了一个支持批处理的20位素数。

parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
SEALContext context(parms);
print_parameters(context);
cout << endl;

我们可以通过查看由SEALContext创建的加密参数限定符来验证是否确实启用了批处理。

    auto qualifiers = context.first_context_data()->qualifiers();
    cout << "Batching enabled: " << boolalpha << qualifiers.using_batching << endl;

    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_keys);
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);

通过上述BatchEncoder 实例的构造,构造批处理已经完成。

BatchEncoder batch_encoder(context);

批处理“槽slot”的总数等于poly_modulus_degree, N,这些槽被组织成2×(N/2)矩阵,可以对其进行加密和计算。每个槽都包含一个整数 modulo plain_modulus。

    size_t slot_count = batch_encoder.slot_count();
    size_t row_size = slot_count / 2;
    cout << "Plaintext matrix row size: " << row_size << endl;

矩阵明文被简单地作为一个扁平的向量提供给BatchEncoder 的数字。第一 row_size 多的数字组成第一行,其余的组成第二行。这里我们创建以下矩阵:

        [ 0,  1,  2,  3,  0,  0, ...,  0 ]
        [ 4,  5,  6,  7,  0,  0, ...,  0 ]
    vector<uint64_t> pod_matrix(slot_count, 0ULL);
    pod_matrix[0] = 0ULL;
    pod_matrix[1] = 1ULL;
    pod_matrix[2] = 2ULL;
    pod_matrix[3] = 3ULL;
    pod_matrix[row_size] = 4ULL;
    pod_matrix[row_size + 1] = 5ULL;
    pod_matrix[row_size + 2] = 6ULL;
    pod_matrix[row_size + 3] = 7ULL;

    cout << "Input plaintext matrix:" << endl;
    print_matrix(pod_matrix, row_size);

首先,我们使用BatchEncoder将矩阵编码为明文多项式。

    Plaintext plain_matrix;
    print_line(__LINE__);
    cout << "Encode plaintext matrix:" << endl;
    batch_encoder.encode(pod_matrix, plain_matrix);

我们可以立即解码,以验证编码的正确性。注意,还没有进行加密或解密。

vector<uint64_t> pod_result;
cout << "    + Decode plaintext matrix ...... Correct." << endl;
batch_encoder.decode(plain_matrix, pod_result);
print_matrix(pod_result, row_size);

接下来我们加密编码后的明文。

    Ciphertext encrypted_matrix;
    print_line(__LINE__);
    cout << "Encrypt plain_matrix to encrypted_matrix." << endl;
    encryptor.encrypt(plain_matrix, encrypted_matrix);
    cout << "    + Noise budget in encrypted_matrix: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
         << endl;

对密文的操作导致在所有8192个槽(矩阵元素)中同时执行同态操作。为了说明这一点,我们构造了另一个明文矩阵:

[ 1,  2,  1,  2,  1,  2, ..., 2 ]
[ 1,  2,  1,  2,  1,  2, ..., 2 ]

并将其编码为明文。

 vector<uint64_t> pod_matrix2;
for (size_t i = 0; i < slot_count; i++)
{
    
    
    pod_matrix2.push_back((i % 2) + 1);
}
Plaintext plain_matrix2;
batch_encoder.encode(pod_matrix2, plain_matrix2);
cout << endl;
cout << "Second input plaintext matrix:" << endl;
print_matrix(pod_matrix2, row_size);

现在,我们将第二个(明文)矩阵添加到加密矩阵中,并将其平方

 print_line(__LINE__);
cout << "Sum, square, and relinearize." << endl;
evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2);
evaluator.square_inplace(encrypted_matrix);
evaluator.relinearize_inplace(encrypted_matrix, relin_keys);

输出:我们还剩下多少噪音预算?

cout << "    + Noise budget in result: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;

接下来,解密并分解明文以将结果恢复为矩阵。

    Plaintext plain_result;
    print_line(__LINE__);
    cout << "Decrypt and decode result." << endl;
    decryptor.decrypt(encrypted_matrix, plain_result);
    batch_encoder.decode(plain_result, pod_result);
    cout << "    + Result plaintext matrix ...... Correct." << endl;
    print_matrix(pod_result, row_size);

当所需的加密计算高度可并行化时,批处理允许我们有效地使用全明文多项式。但是,它并没有解决这个文件开头提到的另一个问题:每个槽只包含一个整数模的明文模量,除非明文模量非常大,否则我们可以很快遇到数据类型溢出,并在需要整数计算时会得到意外的结果。注意,溢出并不能以加密的形式检测到。CKKS方案(以及CKKSEncoder)解决了数据类型溢出问题,但代价是只产生近似的结果。

void example_ckks_encoder()

example_ckks_encoder演示了用于计算加密的实数或复数的Cheon-Kim-Kim-Song
(CKKS)方案。我们首先为CKKS方案创建加密参数。与BFV方案相比,有两个重要的区别:

(1) CKKS不使用明文模数加密参数;
(2)在使用CKKS方案时,以特定的方式选择coeff_modules是非常重要的。我们将在“ckks_basic.cpp”文件中进一步解释这一点。在这个例子中,我们使用CoeffModulus::Create来生成5个40位素数。

这是其有别于batch_encoder的地方,不过两者作用大同小异,限于时间,暂时不对其展开分析了,这篇分析报告到此告一段落。

猜你喜欢

转载自blog.csdn.net/ldxcsdn/article/details/122163346