SEAL 学习第二天: encoder处理

SEAL 学习第二天 encoders


BatchEncoder

[BatchEncoder] (For BFV scheme only)

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

   (1)实际应用中一般采用整数或实数算法,而不是模运算;

   (2)我们只使用了明文多项式的一个系数。这是非常浪费的,因为明文多项式很大,而且在任何情况下都将全部加密。

对于(1) 若增大plain_modulus ,虽然可行,但同时增加了 噪声预算的消耗(当噪声预算消耗到0时,计算结果是不正确的,所以我们要随时关注噪声预算),

将数据编码为明文(plaintext) 进行encoder 允许更多的计算而没有数据类型溢出,还可以允许使用完整的明文多项式。

调用参数解释说明
BatchEncoder batch_encoder(context);SEALContext声明一个批处理类
batch_encoder.encode(pod_matrix, plain_matrix);VectorPlaintext将一个vector批量编码成成明文Plaintext
batch_encoder.decode(plain_matrix, pod_result);Plaintext,Vector将一个明文Plaintext处理解码成vector
/*
设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_modulus设置为一个素数,等于1模2* poly_modulus_degrees。Microsoft SEAL提供了一个助手

找到这样一个质数的方法。在这个例子中,我们创建了一个20位素数支持批处理。

通过CoeffModulus::BFVDefault(poly_modulus_degree) 产出一个素数

parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));

SEALContext context(parms);// 声明完context后类,后就可以开始使用了


// 检测是否成功启用
auto qualifiers = context.first_context_data()->qualifiers();
cout << "Batching enabled: " << boolalpha << qualifiers.using_batching << endl;

批处理‘slots’的总数等于poly_modulus_degree, N,这些’slots’被组织成2×(N/2)矩阵,可以被加密和计算。每个slot含一个整数 MOD plain_modulus。

2×(N/2)

size_t slot_count = batch_encoder.slot_count();
size_t row_size = slot_count / 2;
cout << "Plaintext matrix slot_count size: " << slot_count << endl;
cout << "Plaintext matrix row size: " << row_size << endl;
/*
* 声明一个vector,长度为slot_count, 默为 0 
* 形式为: 2*(slot_count/2) 的二维矩阵
* [0...............row_size]
* [row_size......slot_count]
*/
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不会打印矩阵的每一列,只打印每一行的前5个和最后5个
* [ 0,  1,  2,  3,  0,  0, ...,  0 ]
* [ 4,  5,  6,  7,  0,  0, ...,  0 ]
*/
print_matrix(pod_matrix, row_size);

encode

从给定的矩阵创建一个明文,"批量处理"一个给定的矩阵 Mod (plaintext modulus) 然后将结果存储到
plaintext类,输入的vecotr的大小 <= 多项式模的次数(poly_modulus_degree)

batch_encoder.encode(pod_matrix, plain_matrix);

简单样例验证

生成矩阵A和矩阵B

     矩阵A形式:
     [ 0,  1,  2,  3,  0,  0, ...,  0 ]
     [ 4,  5,  6,  7,  0,  0, ...,  0 ]
     
     矩阵B形式:
     [ 1,  2,  1,  2,  1,  2, ..., 2 ]
     [ 1,  2,  1,  2,  1,  2, ..., 2 ](A+B)^2 并验证结果
     
	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;

    /*
    首先,我们使用BatchEncoder将矩阵编码成一个明文多项式。
    */
    Plaintext plain_matrix;
    print_line(__LINE__);
    cout << "Encode plaintext matrix:" << endl;
    /*
     从给定的矩阵创建一个明文,"批量处理"一个给定的矩阵 Mod (plaintext modulus) 然后将结果存储到
     plaintext类,输入的vecotr的大小 <= 多项式模的次数(poly_modulus_degree)
    */
    batch_encoder.encode(pod_matrix, plain_matrix);

    /*
    接下来,我们加密已编码的明文。
    */
    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(poly_modulus_degree)个slot(矩阵元素)中同时执行同态操作。为了说明这一点,我们构造了另一个明文矩阵:


         [ 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 & size_t(0x1)) + 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);
    
    /*
     现在我们将第二个(明文)矩阵添加到加密矩阵中,并求sum^2。
     */
    print_line(__LINE__);
    cout << "Sum, square, and relinearize." << endl;
    evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2); // encrypted_matrix = ncrypted_matrix + plain_matrix2
    evaluator.square_inplace(encrypted_matrix);// encrypted_matrix = encrypted_matrix * 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);
    

    print_matrix(pod_result, row_size); // 验证结果

image-20210125165134437

总结:

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

CKKSEncoder

[CKKSEncoder] (For CKKS scheme only)
实数或复数形式

(1) CKKS不使用plain_modulus加密参数;

(2) 当使用CKKS方案时,选择coeff_modulus是非常重要的

声明方式 同bfv一致,

EncryptionParameters parms(scheme_type::ckks);// 使用ckks

size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 40, 40, 40, 40, 40 }));// 产生5个40位的素数的数组

CKKS 中 slot数目 = poly_modulus_degree / 2 每个slot编码为一个实数或复数。

而在BFV中 slot = poly_modulus_degree 并且被处理成 一个2X(N/2)的矩阵

size_t slot_count = encoder.slot_count();

要创建CKKS纯文本,我们需要一个特殊的编码器:

BatchEncoder不适用于CKKS
CKKSEncoder 的方式是将实数或复数的向量编码为Plaintext objects 然后对object就可以加密了

函数调用参数类型解释说明
CKKSEncoder encoder(context);SEALContext声明一个CKKS编码器
encoder.encode(input, scale, plain);Vector,scale,Plaintext将vector 按照scale定义的编码精度的尺度参数 编码成 Plaintext
encoder.decode(plain, output);Plaintext,Vector将明文Plaintext解密成Vector

编码与解码

Plaintext plain;
double scale = pow(2.0, 30);
print_line(__LINE__);
cout << "Encode input vector. and the size of scale: "<< scale << endl;
encoder.encode(input, scale, plain); // 将vector 按照scale定义的编码精度的尺度参数 编码成 Plaintext

/*
 我们可以立即解码以检查编码的正确性。
*/
vector<double> output;
cout << "    + Decode input vector ...... Correct." << endl;
encoder.decode(plain, output); // 解码
print_vector(output);

image-20210127101356129

做个简单样例验证, 将输入的vector加密后 求平方 并 序列化后 解码验证结果

    /*
    The vector is encrypted the same was as in BFV.
    简单样例:
        将输入的 vector 加密后 求平方 并 序列化
    */
    Ciphertext encrypted;
    print_line(__LINE__);
    cout << "Encrypt input vector, square, and relinearize." << endl;

    encryptor.encrypt(plain, encrypted); // 加密 Plaintext -> Ciphertext
    // 计算
    evaluator.square_inplace(encrypted); // encrypted = encrypted * encrypted

    evaluator.relinearize_inplace(encrypted, relin_keys); // 序列化

    cout << "    + Scale in squared input: " << encrypted.scale() << " (" << log2(encrypted.scale()) << " bits)"
        << endl;

    print_line(__LINE__);
    cout << "Decrypt and decode." << endl;
    decryptor.decrypt(encrypted, plain); // 解密
    encoder.decode(plain, output); // 解码
    cout << "    + Result vector ...... Correct." << endl;
    print_vector(output); // 结果

验证结果正确

image-20210127102614371

全部源代码

#include "examples.h"


using namespace std;
using namespace seal;

void example_batch_encoder() {
    print_example_banner("Example: Encoders / Batch Encoder");
     
    /*
    [BatchEncoder] (For BFV scheme only)

    Let N 表示 poly_modulus_degree ; T 表示 plain_modulus. Batching
    allows the BFV plaintext polynomials to be viewed as 2-by-(N/2) matrices, with
    each element an integer modulo T. 在矩阵视图中,加密操作法案在加密的矩阵上,
    allowing the user to obtain speeds-ups of several orders of magnitude in fully vectorizable computations. 
    Thus, in all but the simplest computations, batching should be the preferred method to use
    with BFV, and when used properly will result in implementations outperforming
    anything done without batching.
    */

    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));

    parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20)); 
    //PlainModulus::Batching(poly_modulus_degree, 20) 产生20位的素数

    SEALContext context(parms);
    print_parameters(context);
    cout << endl;

    // 检测是否成功启用
    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 batch_encoder(context);

    size_t slot_count = batch_encoder.slot_count();
    size_t row_size = slot_count / 2;
    cout << "Plaintext matrix slot_count size: " << slot_count << endl;
    cout << "Plaintext matrix row size: " << row_size << endl;
    /*
    * 声明一个vector,长度为slot_count, 默为 0 
    * 形式为: 2*(slot_count/2) 的二维矩阵
    * [0...............row_size]
    * [row_size......slot_count]
    */
    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不会打印矩阵的每一列,只打印每一行的前5个和最后5个
    * [ 0,  1,  2,  3,  0,  0, ...,  0 ]
    * [ 4,  5,  6,  7,  0,  0, ...,  0 ]
    */
    print_matrix(pod_matrix, row_size);

    /*
    首先,我们使用BatchEncoder将矩阵编码成一个明文多项式。
    */
    Plaintext plain_matrix;
    print_line(__LINE__);
    cout << "Encode plaintext matrix:" << endl;
    /*
     从给定的矩阵创建一个明文,"批量处理"一个给定的矩阵 Mod (plaintext modulus) 然后将结果存储到
     plaintext类,输入的vecotr的大小 <= 多项式模的次数(poly_modulus_degree)
    */
    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(poly_modulus_degree)个slot(矩阵元素)中同时执行同态操作。为了说明这一点,我们构造了另一个明文矩阵:


         [ 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 & size_t(0x1)) + 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);
    
    /*
     现在我们将第二个(明文)矩阵添加到加密矩阵中,并求sum^2。
     */
    print_line(__LINE__);
    cout << "Sum, square, and relinearize." << endl;
    evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2); // encrypted_matrix = ncrypted_matrix + plain_matrix2
    evaluator.square_inplace(encrypted_matrix);// encrypted_matrix = encrypted_matrix * 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() {
    print_example_banner("Example: Encoders / CKKS Encoder");

    EncryptionParameters parms(scheme_type::ckks);// 使用ckks
    size_t poly_modulus_degree = 8192;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 40, 40, 40, 40, 40 }));// 产生5个40位的素数的数组
    
    // 同BFV一致,声明
    SEALContext context(parms);
    print_parameters(context);
    cout << endl;

    // 密钥产生的方式和 bfv 是一样的
    KeyGenerator keygen(context);
    auto 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);

    /*
    * 要创建CKKS纯文本,我们需要一个特殊的编码器: 
       BatchEncoder不适用于CKKS
       CKKSEncoder 的方式是将实数或复数的向量编码为Plaintext objects 然后对object就可以加密了
    看起来很像BatchEncoder为BFV方案所做的,但理论上它的背后是完全不同的。
    */
    CKKSEncoder encoder(context);

    /*
    * CKKS 中 slot数目  =  poly_modulus_degree / 2  每个slot编码为一个实数或复数。
    * 而在BFV中 slot = poly_modulus_degree 并且被处理成 一个2X(N/2)的矩阵
    */
    size_t slot_count = encoder.slot_count();
    cout << "Number of slots: " << slot_count << endl;


    vector<double> input{ 0.0, 1.1, 2.2, 3.3 };
    cout << "Input vector: " << endl;
    print_vector(input);
    /*
    * 现在我们用CKKSEncoder进行编码。输入的浮点系数将通过参数“scale”放大
    * 这是必要的,因为即使是在纯文本元素的CKKS方案,基本上是多项式整数系数。
    * 在CKKS中,消息是存 MOD coeff_modulus,(而在BFV中是 存储 MOD plain_modulus)
    * 因此,缩放后的消息不能太接近总大小coeff_modulus。
    * 
    */
    Plaintext plain;
    double scale = pow(2.0, 30);
    print_line(__LINE__);
    cout << "Encode input vector. and the size of scale: "<< scale << endl;
    encoder.encode(input, scale, plain); // 将vector 按照scale定义的编码精度的尺度参数 编码成 Plaintext

    /*
    我们可以立即解码以检查编码的正确性。
    */
    vector<double> output;
    cout << "    + Decode input vector ...... Correct." << endl;
    encoder.decode(plain, output); // 解码
    print_vector(output);

    /*
    The vector is encrypted the same was as in BFV.
    简单样例:
        将输入的 vector 加密后 求平方 并 序列化
    */
    Ciphertext encrypted;
    print_line(__LINE__);
    cout << "Encrypt input vector, square, and relinearize." << endl;

    encryptor.encrypt(plain, encrypted); // 加密 Plaintext -> Ciphertext
    // 计算
    evaluator.square_inplace(encrypted); // encrypted = encrypted * encrypted

    evaluator.relinearize_inplace(encrypted, relin_keys); // 序列化

    cout << "    + Scale in squared input: " << encrypted.scale() << " (" << log2(encrypted.scale()) << " bits)"
        << endl;

    print_line(__LINE__);
    cout << "Decrypt and decode." << endl;
    decryptor.decrypt(encrypted, plain); // 解密
    encoder.decode(plain, output); // 解码
    cout << "    + Result vector ...... Correct." << endl;
    print_vector(output); // 结果
    /*
    * CKKS方案允许减少加密计算之间的规模。这是CKKS非常强大和关键的基本特性灵活。
    * 我们将在' 3_levels中详细讨论它。cpp'和稍后“4 _ckks_basics.cpp”。
    */

}
void example_encoders()
{
    print_example_banner("Example: Encoders");

    /*
    Run all encoder examples.
    */
    //example_batch_encoder();
    example_ckks_encoder();
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值