摘要
Hoisting在一个密文需要旋转多次的时候,可以预先计算公共的部分,从而加速整个旋转操作。
本文简要介绍RLWE同态加密的旋转(Rotation)操作和Hoisting的优化原理,然后给出如何在OpenFHE同态加密库中使用Hoisting Rotation。
密文旋转
RLWE同态密文旋转主要有下面三步:
- 自同态:通过自同态将密文槽旋转,即计算 ϕ ( c t ) \phi(ct) ϕ(ct);
- 数值分解(break into digits):将密文的系数用 δ \delta δ进制表示 ϕ ( c t ) = ∑ q i δ i \phi(ct)=\sum q_i \delta^i ϕ(ct)=∑qiδi。其中 δ \delta δ是一个小的整数, q i q_i qi的系数小于 δ \delta δ;
- 密钥交换:将旋转后的密文的解密密钥 ϕ ( s k ) \phi(sk) ϕ(sk),通过密钥交换,变为原来的密钥 s k sk sk。
密文旋转的本质就是一个自同态映射 ϕ \phi ϕ。我们使用 c t ct ct表示密文,那么旋转后的密文 c t 1 = ϕ ( c t ) ct_1=\phi(ct) ct1=ϕ(ct). 这时的密文中的明文已经移到对应的位置了,但是解密密钥却变为了 ϕ ( s k ) \phi(sk) ϕ(sk).
因此,第2、3步是为了将解密密钥重新切换为 s k sk sk.
第2步的目的是降低密钥交换的噪声。
Hoisting Rotation
Hoisting Rotation 可以翻译成提升旋转,或者快速旋转。通过将多个旋转的公共部分计算,提到前面计算,降低整体的开销。为此,我们需要对上面的密文旋转步骤做调整。我们先计算第2步,然后再计算第1步,最后计算第3步:
- 数值分解:首先对原来的密文分解 c t = ∑ q i ′ δ i ct=\sum q_i^\prime \delta^i ct=∑qi′δi;
- 自同态:对分解后的密文进行自同态计算 ϕ ( q i ′ ) \phi(q_i^\prime) ϕ(qi′);
- 密钥交换:使用 ϕ ( q i ′ ) \phi(q_i^\prime) ϕ(qi′)进行密钥交换。
这里的正确性由下面两点保证:
- 自同态对加法和乘法具有分配律,即 ϕ ( λ 1 a + λ 2 b ) = λ 1 ϕ ( a ) + λ 2 ϕ ( b ) \phi(\lambda_1a+\lambda_2b)=\lambda_1\phi(a)+\lambda_2\phi(b) ϕ(λ1a+λ2b)=λ1ϕ(a)+λ2ϕ(b),其中 λ 1 , λ 2 \lambda_1,\lambda_2 λ1,λ2是常数;
- 自同态不会明显增加环上多项式的范数,也就是,如果 q < δ q<\delta q<δ,那么 ϕ ( q ) < λ δ \phi(q)<\lambda \delta ϕ(q)<λδ,其中 λ \lambda λ是一个与环相关的常数。
所以,我们有:
ϕ ( c t ) = ϕ ( ∑ q i ′ δ i ) = ∑ ϕ ( q i ′ ) δ i \phi(ct)=\phi(\sum q_i^\prime \delta^i)=\sum \phi(q_i^\prime)\delta^i ϕ(ct)=ϕ(∑qi′δi)=∑ϕ(qi′)δi.
并且, ϕ ( q i ′ ) \phi(q_i^\prime) ϕ(qi′)是的系数是小整数。
这样第一步对于所有的旋转步数来说,都是一样的,可以提到前面进行计算。
但是这里存在一个问题,就是原来的旋转的自同态只需要执行一次,而交换顺序后,自同态需要作用到每一个 q i ′ q^\prime_i qi′。
所幸的是,多项式在大多数的密码学库都是采用的DCRT的形式表示(系数使用整数中国剩余定理分解,多项式使用多项式的中国剩余定理分解)。在这种表示下,自同态是非常快的。而数值分解需要进行多项式表示的转换,耗费的时间比较多。
因此,Hoisting Rotation可以加快整体的旋转。
OpenFHE代码示例
// OpenFHE example by zyf
#include<iostream>
#include<time.h>
#include <chrono>
#include "openfhe.h"using namespace std;
using namespace lbcrypto;// This example shows that how to rotation in CKKS,
// and how to use hoisting rot
int main(){// var for computation timechrono::high_resolution_clock::time_point time_start, time_end;chrono::microseconds time_diff;CCParams<CryptoContextCKKSRNS> parameters;parameters.SetMultiplicativeDepth(2);parameters.SetScalingModSize(40);CryptoContext<DCRTPoly> cc=GenCryptoContext(parameters);cc->Enable(PKE);cc->Enable(KEYSWITCH);cc->Enable(LEVELEDSHE);usint cycOrder=cc->GetCyclotomicOrder();cout<<"The order of ring is "<<cycOrder<<endl;KeyPair<DCRTPoly> kp=cc->KeyGen();//just rot 1,2,3,4,5,6,7,8 stepscc->EvalRotateKeyGen(kp.secretKey,{1,2,3,4,5,6,7,8});size_t num=8;vector<double> vec_double={1,2,3,4,5,6,7,8};Plaintext p1=cc->MakeCKKSPackedPlaintext(vec_double);auto c0=cc->Encrypt(kp.publicKey,p1);vector<Ciphertext<DCRTPoly>> rotated_c(num);vector<Ciphertext<DCRTPoly>> hoist_rotated_c(num);cout<<"Rotation"<<endl;time_start=chrono::high_resolution_clock::now();for(size_t i=0;i<num;i++){rotated_c[i]=cc->EvalRotate(c0,i+1);}time_end=chrono::high_resolution_clock::now();time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);cout << "The time is [" << time_diff.count() << " microseconds]" << endl;Plaintext result;for(size_t i=0;i<num;i++){cc->Decrypt(kp.secretKey,rotated_c[i],&result);result->SetLength(num);cout<<result<<endl;}cout<<"Hoisting Rotation"<<endl;time_start=chrono::high_resolution_clock::now();auto pre_c=cc->EvalFastRotationPrecompute(c0);for(size_t i=0;i<num;i++){hoist_rotated_c[i]=cc->EvalFastRotation(c0,i+1,cycOrder,pre_c);}time_end=chrono::high_resolution_clock::now();time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);cout << "The time is [" << time_diff.count() << " microseconds]" << endl;for(size_t i=0;i<num;i++){cc->Decrypt(kp.secretKey,rotated_c[i],&result);result->SetLength(num);cout<<result<<endl;}}
在我的机器上,普通的旋转8次,需要93613 microseconds,而Hoisting Rotation需要60798 microseconds。