[FHE]使用Microsoft SEAL库实现的BFV方案写一个demo
Description
用SEAL中实现的BFV方案完成一个面向整数的同态加密方案。
初始化
设置公开参数
所有的参数包含在一个EncryptionParameter对象当中,初始化一个对象打包了三个参数poly_modulus_degree、coeff_modulus和plain modulus。
poly_modulus_degree:即多项式环\(\mathcal{R}=\mathbf{Z}[X]/X^N+1\)中的\(N\);
coeff_modulus:即\(q\),和密钥与加密过程中选取的随机数有关;
plain modulus:即明文模数,决定了明文空间\(\mathcal{R}_{t}=\mathbf{Z}_{t}[X]/X^N+1\)。
参数对象的初始化可以用以下初始化语句完成:
EncryptionParameters parms(scheme_type::bfv);
表明初始化一个BFV方案的参数对象。
poly_modulus_degree为素数幂的形式,一般而言设置成2的幂次,方便分圆多项式的计算。SEAL中可以选择的值为1024、2048 、4096、8192或者更大,选择很小的值会报错。设置分圆多项式次数可以用以下方法:
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
coefficient modulus需要一个很大的整数,用来使噪声预算更加宽裕(因为噪声控制的要求是\(m+te<q\))。coefficient modulus的长度(以比特计算)上限由poly_modulus_degree决定,由于越大越好,其实这个参数不用费脑子去取,只需要用小帮手函数:
CoeffModulus::BFVDefault(poly_modulus_degree);
就可以了。
plain_modulus是BFV特有的(相较于CKKS)。是一个越小越好的正整数(但是会影响明文空间),可以取素数或者2的幂次。可以直接设置:
parms.set_plain_modulus(1024);
也可以使用小帮手生成一个合适的素数:
PlainModulus::Batching(poly_modulus_degree, bit_size);
bit_size是生成的素数的长度(以比特计)。
初始化加密方案对象
接受一个参数对象,可以初始化一个SEALContext对象,期间会检查参数,如果不符合要求就会报错。
SEALContext context(parms);
接着需要实例化一个KeyGenerator类来生成公私钥对。
KeyGenerator keygen(context);
SecretKey secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
为了实现旋转操作还需要旋转密钥:
GaloisKeys galois_keys;
keygen.create_galois_keys(galois_keys);
为了实现重线性化还需要重线性化的辅助密钥(计算密钥):
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
接着用上面实例化好了的各种对象生成其他各种对象:加密器、解密器、求值器,只能说SEAL的逻辑还是很清楚明白的。
// 加密器
Encryptor encryptor(context, public_key);
// 解密器
Decryptor decryptor(context, secret_key);
// 求值器
Evaluator evaluator(context);
算子
根据src/seal/evaluate.h头文件可以查看求值器类的所有函数,当文档看(有选择)。
| 算子 | Description |
|---|---|
| negate_inplace | 取负 |
| add_inplace | 加上一个密文 |
| sub_inplace | 减去一个密文 |
| multiply_inplace | 乘上一个密文 |
| square_inplace | 平方 |
| relinearize_inplace | 重线性化 |
| exponentiate_inplace | 幂次 |
| add_plain_inplace | 加上一个明文 |
| sub_plain_inplace | 减去一个明文 |
| multiply_plain_inplace | 乘上一个明文 |
| rotate_rows_inplace | 矩阵行旋转 |
| rotate_columns_inplace | 矩阵列旋转 |
| rotate_vector_inplace | 向量旋转 |
实在太多了就只捡了点比较重要的。inplace的意思是直接替换,不加inplace就需要加一个输入存储结果。
计算过程
单个数据编码
BFV对于单个数据的编码的方式就是拆成进制表示,将对应进制位的数作为对应次数单项式的系数。如果是浮点数就将小数部分按照负幂次进行拆分,然后模上多项式\(X^N+1\)。
这里的意思就是将数字6表示为16进制编码后,将编码的值(0x)0006转为string,当作多项式的系数表示输入。
uint64_t x = 6;
Plaintext x_plain(uint64_to_hex_string(x));
SIMD编码
BFV的SIMD编码和CKKS差别比较大,用的是多项式环上的中国剩余定理,将向量编码为多项式。
向量以vector进行存储,编码的多项式也以系数方式以vector进行存储。编码后的形式一律为\(2 \times (N/2)\)的矩阵,其中\(N=\)poly_modulus_degree,并以一个长度为\(N\)的vector的形式进行存储。
// 先实例化编码器
BatchEncoder batch_encoder(context);
vector<uint64_t> v(slot_count, 0ULL);
batch_encoder.encode(v, plain_matrix);
解密
先初始化一个明文对象,储存结果。用编码器就可以直接解密,编码器的实例化过程使用了私钥:
decryptor.decrypt(encrypted_result, decrypted_result);
解码
单个数据不需要解码,解密后得到的就是16进制表示的string。
BatchEncoder的解码则直接使用decode方法就好,将vector存储的系数表示多项式解码会vector存储的向量。
batch_encoder.decode(plain_result, decode_result);
加法与乘法
evaluator.add_plain(encrypted, plain, res);
evaluator.add(encrypted1, encrypted2, res);
evaluator.multiply_plain(encrypted, plain, res);
evaluator.multiply(encrypted1, encrypted2, res);
乘法之后需要重线性化以解决密文维数过大的问题。
运算需要注意控制噪声,模切换在BFV中不是必须的。
旋转
旋转需要rotation key,即生成的Galois key。
evaluator.rotate_rows_inplace(encrypted_matrix, 3, galois_keys);
evaluator.rotate_columns_inplace(encrypted_matrix, galois_keys);
evaluator.rotate_vector_inplace(encrypted_vector, galois_keys);
每一个2的幂次的旋转步长需要一个对应的Galois Key,非2的幂次的旋转步长表示成多个2的幂次相加的形式。这是由slots的数目是2的幂次决定的。
乘法、密文维数与重线性化
密文-密文乘法、平方幂次运算都会造成密文维数膨胀。
直接对密文对象使用size()方法可以查看密文的维数:
x.size()
这样可以重线性化(3维 \(\rightarrow\) 2维):
evaluator.relinearize_inplace(x, relin_keys);
噪声预算
使用如下方法可以查看密文还可以增加的噪声(以比特计):
decryptor.invariant_noise_budget(x)

浙公网安备 33010602011771号