Lattigo之浮点数加解密
先简单回顾,CKKS(Cheon-Kim-Kim-Song)加密方案是一种在同态加密领域中广泛应用的方案,特别是在处理实数和复数上的计算时表现出了优异的性能。CKKS方案允许对编码后的近似实数或复数进行加密,然后进行同态加密操作(如加法和乘法),最后能够解密出一个近似的结果。这种加密方案特别适合用于需要处理大量实数数据的机器学习和数据分析场景。
CKKS方案的核心步骤是通过一系列复杂的代数和数论操作来实现的。下面是这个方案的关键步骤和相关公式的一个概览。请注意,实际的CKKS实现涉及更复杂的数学,并且这里的描述只是为了提供一个概念框架。
CKKS方案
1. 参数设置和密钥生成
首先,选择一个安全参数 λ λ λ,然后根据 λ λ λ 选择多项式的度数 N N N(通常是 2 2 2的幂)以及一系列模数 ( q 1 q_1 q1, q 2 q_2 q2, ..., q L q_L qL ) 来构建模数链。接下来,生成密钥对:
- 私钥 sk: 随机选择一个小项多项式 ( s ∈ R ) ( s \in R ) (s∈R)。
- 公钥 pk: 计算 ( ( p 0 , p 1 ) ) ( (p_0, p_1) ) ((p0,p1)) 其中 ( p 0 , p 1 ∈ R q ) ( p_0, p_1 \in R_q ) (p0,p1∈Rq) 并且 ( p 0 = − a s + e + Δ m ) ( p_0 = -as + e + \Delta m ) (p0=−as+e+Δm) (其中 ( a ) ( a ) (a) 是随机选择的, ( e ) ( e ) (e) 是一个小项误差多项式,( Δ \Delta Δ ) 是用来调整比例的大整数,以便于编码实数)。
2. 加密过程
加密一个复数向量 ( m ⃗ ) ( \vec{m} ) (m ) 的步骤如下:
-
编码:首先,将复数向量 $( \vec{m} ) $编码到一个多项式 ( m ( x ) ) ( m(x) ) (m(x)) 中。
-
加密:然后计算密文 $c = (c_0, c_1) $ 其中:
[ c 0 = p 0 ⋅ r + e 0 + Δ ⋅ m ( x ) m o d q ] [ c_0 = p_0 \cdot r + e_0 + \Delta \cdot m(x) \mod q ] [c0=p0⋅r+e0+Δ⋅m(x)modq]
[ c 1 = p 1 ⋅ r + e 1 m o d q ] [ c_1 = p_1 \cdot r + e_1 \mod q ] [c1=p1⋅r+e1modq]这里的 ( r r r ) 是随机选择的,( e 0 e_0 e0 ) 和 ($ e_1$ ) 是小项误差多项式。
3. 同态运算
CKKS方案支持同态加法和乘法:
-
同态加法:给定两个密文 ( $c^{(1)} = (c_0^{(1)}, c_1^{(1)}) $) 和 ( $c^{(2)} = (c_0^{(2)}, c_1^{(2)}) $),它们的和为:
[ c ( 1 + 2 ) = ( c 0 ( 1 ) + c 0 ( 2 ) , c 1 ( 1 ) + c 1 ( 2 ) ) ] [ c^{(1+2)} = (c_0^{(1)} + c_0^{(2)}, c_1^{(1)} + c_1^{(2)}) ] [c(1+2)=(c0(1)+c0(2),c1(1)+c1(2))]
-
同态乘法:对于乘法,通常需要执行relinearization和rescaling步骤来保持密文的可管理大小。这里省略具体步骤的描述。
4. 解密过程
解密一个密文 ( c = ( c 0 , c 1 ) c = (c_0, c_1) c=(c0,c1) ),使用私钥 ($ s $) 来计算:
[ m ( x ) = ( c 0 + c 1 ⋅ s ) m o d q m o d x N + 1 ] [ m(x) = \left( c_0 + c_1 \cdot s \right) \mod q \mod x^N + 1 ] [m(x)=(c0+c1⋅s)modqmodxN+1]
然后从多项式 ( m(x) ) 中解码出原始的复数向量。
5. 解码
解码过程涉及到从多项式 ( m ( x ) m(x) m(x) ) 中恢复出编码前的向量 ( m ⃗ \vec{m} m )。这通常包括舍去多项式系数的缩放因子 ( Δ \Delta Δ ) 以及将多项式的系数转换回复数。
CKKS方案的详细实现比上述描述要复杂得多,在这里我只做简要回顾,有兴趣的还是读原文Homomorphic Encryption for Arithmetic of Approximate Numbers (iacr.org)。
CKKS加解密浮点数实例
接下来我们具体实现一个CKKS加密的例子,我们随机生成 X X X向量和 Y Y Y向量,我们对两个变量执行 ( X − Y ) 2 (X-Y)^2 (X−Y)2并打印输出最终结果。
-
声明参数和钥匙
gopackage main import ( "fmt" "math/rand" "github.com/tuneinsight/lattigo/v5/core/rlwe" "github.com/tuneinsight/lattigo/v5/he/hefloat" ) func main() { //paramter var err error var params hefloat.Parameters if params, err = hefloat.NewParametersFromLiteral( hefloat.ParametersLiteral{ LogN: 14, // log2(ring degree) LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus) LogP: []int{61}, // log2(primes P) (auxiliary modulus) LogDefaultScale: 45, // log2(scale) }); err != nil { panic(err) } // Key Generator keygen := rlwe.NewKeyGenerator(params) sk := keygen.GenSecretKeyNew() encoder := hefloat.NewEncoder(params) encryptor := rlwe.NewEncryptor(params, sk) decryptor := rlwe.NewDecryptor(params, sk) rlk := keygen.GenRelinearizationKeyNew(sk) evk := rlwe.NewMemEvaluationKeySet(rlk) evaluator := hefloat.NewEvaluator(params, evk)
-
构造数据并加密
go// generate data x := make([]float64, params.MaxSlots()) y := make([]float64, params.MaxSlots()) r1 := rand.New(rand.NewSource(0)) for i := range x { x[i] = r1.Float64() } r2 := rand.New(rand.NewSource(1)) for i := range y { y[i] = r2.Float64() } // encoder and encryptor plaintext_x := hefloat.NewPlaintext(params, params.MaxLevel()) plaintext_y := hefloat.NewPlaintext(params, params.MaxLevel()) var ct_x *rlwe.Ciphertext var ct_y *rlwe.Ciphertext if err = encoder.Encode(x, plaintext_x); err != nil { panic(err) } if err = encoder.Encode(y, plaintext_y); err != nil { panic(err) } if ct_x, err = encryptor.EncryptNew(plaintext_x); err != nil { panic(err) } if ct_y, err = encryptor.EncryptNew(plaintext_y); err != nil { panic(err) }
-
进行密文计算
go//(x-y) var ct_sub *rlwe.Ciphertext if ct_sub, err = evaluator.SubNew(ct_x, ct_y); err != nil { panic(err) } //(x-y)^2 var ct_square *rlwe.Ciphertext if ct_square, err = evaluator.MulRelinNew(ct_sub, ct_sub); err != nil { panic(err) }
-
显示输出
go//print x and y data fmt.Printf("X: ") for i := 0; i < 4; i++ { fmt.Printf("%f ", x[i]) } fmt.Println() fmt.Printf("Y: ") for i := 0; i < 4; i++ { fmt.Printf("%f ", y[i]) } fmt.Println() //print x-y pt_sub := decryptor.DecryptNew(ct_sub) have := make([]float64, params.MaxSlots()) if err = encoder.Decode(pt_sub, have); err != nil { panic(err) } fmt.Printf("X-Y: ") for i := 0; i < 4; i++ { fmt.Printf("%f ", have[i]) } fmt.Println() //print (x-y)^2 pt_square := decryptor.DecryptNew(ct_square) if err = encoder.Decode(pt_square, have); err != nil { panic(err) } fmt.Printf("(X-Y)^2: ") for i := 0; i < 4; i++ { fmt.Printf("%f ", have[i]) } fmt.Println() }
通过这个例子,我们展现了一些基本的用法,当然基础的用法还包括一些操作比如evaluator.Add()
,但其操作都比较类似,就不赘述了。