bfv同态加密_微软同态加密库SEAL使用

近期刚接触SEAL库,本文记录该库的使用方法,具体的同态加密的原理不过多介绍,若有错误,请各位大佬批评指正。

SEAL(Simple Encrypted Arithmetic Library)是微软开源的同态加密库,基于C++11编写,代码在Github可以找到,额外依赖比较少,下载到本地编译安装就能使用了。

SEAL提供了两种机制的同态加密,BFV和CKKS。我粗略的理解为前者编码整数,而后者可以用于编码浮点数。浮点数的编码,需要scale一定的倍数,将浮点数转换成整数。SEAL采用的同态加密算法基于多项式环,其中有几个重要的参数:

polynomial modulus

coefficient modulus

plaintext modulus (只在BFV机制中)

其中polynomial modulus和max coefficient modulus bit-length有一个对应关系,示例代码中给出了对应关系,比如1024 对应27,8192对应218。SEAL提供了默认设置的函数,其中polynomial modulus是必须设置的。本文主要讨论CKKS机制的用法,针对浮点数进行同态加密。

在SEAL中有一个重要的level的概念,根据示例代码里的注释,可以理解为SEAL根据默认的参数创建了一个modulus switching chain ,在同一个链上的加密实例除了coefficient modulus 其他都相同。下面示例代码给的一个解释:

coeff_modulus: { 50, 30, 30, 50, 50 } +---+ Level 4 (all keys; `key level')

|

coeff_modulus: { 50, 30, 30, 50 } +---+ Level 3 (highest `data level')

|

coeff_modulus: { 50, 30, 30 } +---+ Level 2

|

coeff_modulus: { 50, 30 } +---+ Level 1

|

coeff_modulus: { 50 } +---+ Level 0 (lowest level)

这是通过set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 50, 30, 30, 50, 50 })) 方法设置的。这个我粗略的理解是用于浮点数乘法之后对扩展的倍数rescale,后面会介绍到。

由于SEAL代码用到了SIMD(Single Instruction Multiple Data)的技术加速同态加密操作,我简单的理解就是把多个明文一起加密成一个密文,例如可以把一个vector编码成一个密文Ciphertext。下面给出一个简单的加密操作基础的代码:

//定义加密参数EncryptionParameters encryptionParameters(scheme_type::CKKS);

size_t poly_modulus_degree = 8192;

encryptionParameters.set_poly_modulus_degree(poly_modulus_degree);

encryptionParameters.set_coeff_modulus(CoeffModulus::Create(

poly_modulus_degree, {60,40,40,60}));

double scale = pow(2.0,40);

//创建加密上下文,并初始化相关实例std::shared_ptr context = SEALContext::Create(encryptionParameters);

KeyGenerator keyGenerator(context);

auto public_key = keyGenerator.public_key();

auto secret_key = keyGenerator.secret_key();

auto relin_keys = keyGenerator.relin_keys();

const GaloisKeys galois_keys = keyGenerator.galois_keys();

Encryptor encryptor(context, public_key);

Evaluator evaluator(context);

Decryptor decryptor(context, secret_key);

CKKSEncoder encoder(context);

vector test(1);

test.push_back(1.0);

//加密/解密操作都是在编码后的数据上进行Plaintext test_plain;

Ciphertext test_cipher;

encoder.encode(test,scale, test_plain);

encryptor.encrypt(test_plain, test_cipher);

Plaintext plain_coeff1;

encoder.encode(3.0, scale, plain_coeff1);

evaluator.multiply_plain_inplace(test_cipher,plain_coeff1);

evaluator.relinearize_inplace(test_cipher, relin_keys);

evaluator.rescale_to_next_inplace(test_cipher);

Plaintext plain_test_result;

decryptor.decrypt(test_cipher, plain_test_result);

vector test_result;

encoder.decode(plain_test_result, test_result);

print_vector(test_result, 4, 7);

//输出/*[ 0.0000000, 9.0000000, -0.0000000, -0.0000000, ..., -0.0000000, 0.0000000, -0.0000000, -0.0000000 ]数组长度为 poly_modulus_degree/2*/

基于此,目标是实现一个基于SEAL的矩阵相乘操作,该如何实现。简单的方法对两个矩阵的每个元素进行加密,然后相应乘法。但这样的方式当矩阵较大时,无论是时间还是内存上都难以承受。

基于前面描述的,SEAL可以把一个vector一起加密为一个Ciphertext,这个密文实际上是一个长度为poly_modulus_degree/2长度的vector(看了源码实际上是IntArray) 。在SEAL中,密文上的加法乘法操作都是element-wise的,是对每个元素的操作。实际上SEAL中向量的点积(dot product)是要自己扩展实现的。

[1.0,2.0,3.0,4.0]

//加密后

[c1,c2,c3,c4,...,c(0),c(0),c(0),c(0)] //这是一个密文第一个向量乘第二个矩阵

如上图所示,以向量乘矩阵为例,我们可以把x1,x2,...xn加密成一个Ciphertext c1 ,然后可以把矩阵中的每一列y1,y2,...yn加密成一个Ciphertext c2 。那么调用multiply得到的乘积后的密文,实际上是x1y1,x2y2,...,xnyn加密后的结果(注意这是一个密文)。要求x1y1+x2y2+...+xnyn,需要额外的计算。SEAL中提供了rotate方法,对加密后的密文进行旋转移动。比如 x1,x2,...xn转成xn,x2,...x1 。(知乎怎么编辑公式啊!)利用如下的rotate_sum的方法可以计算得到一个每一项为z=x1y1+x2y2+...+xnyn的密文。

const int poly_modulus_degree_power = log2(poly_modulus_degree)

void rotate_sum(Evaluator& evaluator, Ciphertext& ciphertext, const GaloisKeys& keys){

Ciphertext rotated;

for (int i = 0; i < poly_modulus_degree_power - 1; ++i) {

evaluator.rotate_vector(ciphertext, pow(2,i), keys, rotated);

evaluator.add_inplace(ciphertext, rotated);

}

}类似这么一个过程

以上操作之后,再把这个密文除了第一项其他项数都变为0。这是向量乘以矩阵中的某一列,得到了一个第一项为乘积结果,其他项为0的密文。对于其他列的乘积也是类似,但需要把结果计算到一个密文中,其实就是把第j列的乘积结果向右旋转j位,再把相应的结果相加。第j列的乘积结果向右旋转j位再累加

最终得到z1,z2,...,zn的一个密文(注意这是一个密文 )。这是向量乘矩阵的结果是一个密文,如果是矩阵相乘那么将得到的是一个密文的vector。

我自己的实验中,以一个784的输入向量乘784X200的矩阵,在参数poly_modulus_degree=8192的条件下,这个乘法大约化了40s(笔记本上测试)。其实这里可以看到,对于4096长度的密文长度,我只利用了前784位,还有大量的slot是属于浪费的。这里是不是存在着优化的方法?或者说是否还存在更加适合SEAL的矩阵乘法规则。

由于刚刚接触,还有一些问题。比如对于浮点数,是否是只能支持有限次的乘法,因为每次乘法之后scale实际上是会翻倍,尽管每次可以rescale到相同的倍数,但是这样实际上是下降到了低一层的level上,在经过几次乘法之后就会报超出范围的错。如果有大佬了解原因,还望不吝指教。根据计算的任务选取合适的参数实际上非常重要的,因为这会很大程度地影响SEAL计算的效率。以上只是我个人使用同态加密库的一些记录和心得,如果有错误还请批评指正。

把SEAL用于机器学习的Cryptonets实际上预测的效率也是很低,看最新的Cryptonets的库预测小数据集通常都需要上百秒。在隐私保护机器学习领域还有别的方式,有机会把基于MPC的方案整理总结写成文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值