赞
踩
前面介绍 BFV 和 CKKS 加密方案,这两者更为常用。并且也解释了 Batch Encoder 和 级别的概念,这对接下来演示 BGV 会很有帮助。
BGV (Brakerski-Gentry-Vaikuntanathan) 方案 是一种基于环学习同态加密(RLWE)问题的加密方案。BGV 方案可以实现任意计算电路的同态加密,特别适合于加密数据的复杂运算。
特点:
优点:
缺点:
先看发展顺序:
分别的适用场景:
每种方案都有其独特的优势和适用场景,在实际应用中,选择适合的方案可以最大化地发挥同态加密技术的优势。
在本示例中,计算8次多项式 ,并且在整数 1、2、3、4上的加密
。多项式的系数可以看作是明文输入计算在 plain_modulus == 1032193 模数下进行。
在BGV方案中对加密数据进行计算类似于BFV。这个例子的主要目的是解释BFV和BGV在密文系数模数选择和噪声控制方面的区别。
这里先使用 BFVDefault 创建 coeff_modulus,后面会介绍如何更好的设置。
- EncryptionParameters parms(scheme_type::bgv);
- 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));
- SEALContext context(parms);
-
- 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);

这里想再次强调一下,因为用的是 Batch 批处理,所以在设置 plain_modulus 的时候,要求是与 2倍 poly_modulus_degree 同余 1 的素数,这与普通的 Encoder 要求不同。上面代码中是用 PlainModulus::Batching 自动生成满足条件的随机数。
这里输出设置的参数:
批处理和槽操作在 BFV 和 BGV 中是相同的:
- BatchEncoder batch_encoder(context);
- size_t slot_count = batch_encoder.slot_count();
- size_t row_size = slot_count / 2;
这里特意设置 row_size 变量,是因为之前讲批处理的时候,强调过内部在逻辑上会编码成两行,故其实就是 。当然这个结构对于编码和计算是基本无感的,只有在考虑行旋转和列旋转的时候会有影响,这个下一篇会具体介绍(挖坑 + 1)。
- vector<uint64_t> pod_matrix(slot_count, 0ULL);
- pod_matrix[0] = 1ULL;
- pod_matrix[1] = 2ULL;
- pod_matrix[2] = 3ULL;
- pod_matrix[3] = 4ULL;
- Plaintext x_plain;
- batch_encoder.encode(pod_matrix, x_plain);
这里对编码结果打印输出一下:
- Ciphertext x_encrypted;
- cout << "Encrypt x_plain to x_encrypted." << endl;
- encryptor.encrypt(x_plain, x_encrypted);
- cout << "+ noise budget in freshly encrypted x: " << decryptor.invariant_noise_budget(x_encrypted) << " bits" << endl;
这里先对输入进行加密,并输出噪声预算:
先计算 :
- Ciphertext x_squared;
- evaluator.square(x_encrypted, x_squared);
- cout << "+ size of x_squared: " << x_squared.size() << endl;
- evaluator.relinearize_inplace(x_squared, relin_keys);
- cout << "+ size of x_squared (after relinearization): " << x_squared.size() << endl;
- cout << "+ noise budget in x_squared: " << decryptor.invariant_noise_budget(x_squared) << " bits" << endl;
因为是 密文乘密文,为了减少乘法后的密文大小,这里进行了重新线性化,并输出了噪声预算,同时进行解密验证:
可以看出,运算中间结果是正确的,并且重新线性化后,密文大小从3减小到2。
再计算 :
- Ciphertext x_4th;
- evaluator.square(x_squared, x_4th);
- cout << "+ size of x_4th: " << x_4th.size() << endl;
- evaluator.relinearize_inplace(x_4th, relin_keys);
- cout << "+ size of x_4th (after relinearization): " << x_4th.size() << endl;
- cout << "+ noise budget in x_4th: " << decryptor.invariant_noise_budget(x_4th) << " bits" << endl;
同样进行了重新线性化,并输出目前噪声预算,同时进行解密验证:
可以看出这里的噪声预算下降的特别快,只剩 35 bits 了。
最后计算 :
- Ciphertext x_8th;
- evaluator.square(x_4th, x_8th);
- cout << "+ size of x_8th: " << x_8th.size() << endl;
- evaluator.relinearize_inplace(x_8th, relin_keys);
- cout << "+ size of x_8th (after relinearization): " << x_8th.size() << endl;
- cout << "+ noise budget in x_8th: " << decryptor.invariant_noise_budget(x_8th) << " bits" << endl;
噪声预算已经达到0,这意味着解密无法得到正确的结果。故此,引出 BGV需要模数切换以减少噪声增长!
下面演示在每次重新线性化后插入模数切换:(避免啰嗦,这里直接完整计算)
- cout << "+ noise budget in x_squared (previously): " << decryptor.invariant_noise_budget(x_squared) << " bits" << endl;
- evaluator.square(x_encrypted, x_squared);
- evaluator.relinearize_inplace(x_squared, relin_keys);
- evaluator.mod_switch_to_next_inplace(x_squared);
- cout << "+ noise budget in x_squared (with modulus switching): " << decryptor.invariant_noise_budget(x_squared) << " bits" << endl;
-
- evaluator.square(x_squared, x_4th);
- evaluator.relinearize_inplace(x_4th, relin_keys);
- evaluator.mod_switch_to_next_inplace(x_4th);
- cout << "+ noise budget in x_4th (with modulus switching): " << decryptor.invariant_noise_budget(x_4th) << " bits" << endl;
-
- evaluator.square(x_4th, x_8th);
- evaluator.relinearize_inplace(x_8th, relin_keys);
- evaluator.mod_switch_to_next_inplace(x_8th);
- cout << "+ noise budget in x_8th (with modulus switching): " << decryptor.invariant_noise_budget(x_8th) << " bits" << endl;
-
- decryptor.decrypt(x_8th, decrypted_result);
- batch_encoder.decode(decrypted_result, pod_result);

这里对中间结果也进行解密,并输出其噪声预算的变化:
这里仔细对比可以发现:虽然通过模数切换 x_squared 的噪声预算比之前少,但噪声预算的消耗速率较慢,故最后可以正确解密。
通过之前的介绍实验,我们能发现,有时候进行模数切换会损耗噪声预算,但是进行到一定乘法深度后,再进行切换就不会损耗噪声,这种情况是一定适合加入模数切换的。
同时上面发现虽然降低了 x_squared 的噪声预算,但是噪声预算的消耗减慢,故这种情况也适合加入模数切换。
但是这些不意味着在每次计算后都应该进行模数切换,因为要权衡减少的预算和减缓消耗的速度,最好自己进行实验比对。"故为了在应用中实现噪声预算的最佳消耗速率,需要仔细选择插入模数切换的位置,并手动选择 coeff_modulus。"
下篇介绍对密文进行的 行旋转 和 列旋转(未完待续。。。)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。