2021SC@SDUSC
目录
BGV原理分析
摘要:这篇博文旨在解释当今大多数同态加密(HE)方案背后的基本数学概念,然后在此基础上使用Python从头开始实现类似于BGV的方案。我们将首先介绍大多数今天的他计划背后的难题,即learning with error及其ring-learning with error。
1 符号说明
我们用Zq表示从(−q/2,q/2](q为大于1的整数) 的整数集。并且如果没有说明,所有的整数的操作(加法等等)都需要mod q,
有时简化计算我们把Zq给映射到[0,q)的整数域上去,通过−x≡q−x(mod q),例如−1≡6(mod7),想象一下时钟的例子
同时我们有:
v
∈
Z
q
n
表
示
一
个
n
维
向
量
,
向
量
内
的
元
素
属
于
Z
q
v\in Z_q^n 表示一个n维向量,向量内的元素属于Z_q
v∈Zqn表示一个n维向量,向量内的元素属于Zq
[ . ] m 表 示 m o d m 的 操 作 {[.]_m}表示mod\quad m的操作 [.]m表示modm的操作
< a , b > = ∑ i n a i ⋅ b i ( m o d q ) <a,b>=\sum_i^na_i\cdot b_i(mod\quad q) <a,b>=i∑nai⋅bi(modq)
2 多项式运算
以 Z 11 / < x 4 + 1 > 为 例 以Z_{11}/<x^4+1>为例 以Z11/<x4+1>为例
注意:系数之间的运算都要mod 11,运算得到的多项式都要mod (x^4+1)
-
加法:
-
乘法
3 Learning With Error 与 Ring Learning With Error
LWE就是说根据(ai,bi)去恢复s很困难,因为存在误差。
Ring-LWE问题是LWE问题的变体,它也基于根据(ai,bi)恢复s的困难性,等式也基本相同,但是变量的取值空间从
Z
q
n
Z_q^n
Zqn
变为了多项式系数环:
R
q
=
Z
q
[
x
]
/
<
x
n
+
1
>
R_q=Z_q[x]/<x^n+1>
Rq=Zq[x]/<xn+1>
多项式环的定义:
4 构建一个全同态体系
我们将整个方案分解为基本功能、密钥生成、加密、解密和评估。
首先我们定义两个方法polyadd
与polymul
用于多项式的加与乘,numpy.polynomial
包的相关操作看链接
import numpy as np
from numpy.polynomial import polynomial as poly
def polymul(x, y, modulus, poly_mod):
"""Add two polynoms
Args:
x, y: 两个要相加的多项式.
modulus: 多项式系数的模 Zq中的q.
poly_mod: 多项式模x^n+1.
Returns:
A polynomial in Z_modulus[X]/(poly_mod).
多项式xy相乘后系数mod modulus,然后对多项式模 poly_mod(polydiv[0] 商,polydiv[1]余数,得到的余数多项式再mod modulus)
"""
return np.int64(
np.round(poly.polydiv(poly.polymul(x, y) % modulus, poly_mod)[1] % modulus)
)
def polyadd(x, y, modulus, poly_mod):
"""Multiply two polynoms
Args:
x, y: two polynoms to be multiplied.
modulus: coefficient modulus.
poly_mod: polynomial modulus.
Returns:
A polynomial in Z_modulus[X]/(poly_mod).
"""
return np.int64(
np.round(poly.polydiv(poly.polyadd(x, y) % modulus, poly_mod)[1] % modulus)
)
4.1 Key Generation
因此我们先实现一些采样的方法:
def gen_binary_poly(size):
"""Generates a polynomial with coeffecients in [0, 1]——构造sk
Args:
size: number of coeffcients, size-1为这个多项式的度.
Returns:
array of coefficients with the coeff[i] being
the coeff of x ^ i.
"""
return np.random.randint(0, 2, size, dtype=np.int64)
def gen_uniform_poly(size, modulus):
"""Generates a polynomial with coeffecients being integers in Z_modulus——在Zq范围内随机采样
Args:
size: number of coeffcients, size-1 being the degree of the
polynomial.
Returns:
array of coefficients with the coeff[i] being
the coeff of x ^ i.
"""
return np.random.randint(0, modulus, size, dtype=np.int64)
def gen_normal_poly(size):
"""Generates a polynomial with coeffecients in a normal distribution——一个正态分布
of mean 0 and a standard deviation of 2, then discretize it.
Args:
size: number of coeffcients, size-1 being the degree of the
polynomial.
Returns:
array of coefficients with the coeff[i] being
the coeff of x ^ i.
"""
return np.int64(np.random.normal(0, 2, size=size))
因此我们的KeyGen定义如下:
def keygen(size, modulus, poly_mod):
"""生成pk与sk
Args:
size: size of the polynoms for the public and secret keys.
modulus: coefficient modulus.
poly_mod: polynomial modulus.
Returns:
Public and secret key.
"""
sk = gen_binary_poly(size)
a = gen_uniform_poly(size, modulus)
e = gen_normal_poly(size)
b = polyadd(polymul(-a, sk, modulus, poly_mod), -e, modulus, poly_mod)
return (b, a), sk
公钥pk(b,a)用于加密,私钥sk用于解密
4.2 Encryption
我们的加密方案支持在环Rt上加密,其中t被称为明文模数,我们将简单地将整数pt(明文)编码为常数多项式m(x)=pt(例如,pt=5,m(x)=0*x^size-1+…+5)。
加密方案使用pk∈Rq×Rq和一个明文(plaintext)
多项式m∈Rt,输出一个密文(ciphertext)
ct∈Rq×Rq,用两个多项式 ct0 and ct1表示:
- u:是R2的均匀分布(与sk相同)
- e1,e2:是Rq上的离散高斯分布采样(与KeyGen的error相同)
- δ:q整除t的商。
def encrypt(pk, size, q, t, poly_mod, pt):
"""Encrypt an integer.加密一个整数
Args:
pk: public-key.
size: size of polynomials.
q: ciphertext modulus.
t: plaintext modulus.
poly_mod: polynomial modulus.
pt: integer to be encrypted.
Returns:
Tuple representing a ciphertext.
"""
# encode the integer into a plaintext polynomial
m = np.array([pt] + [0] * (size - 1), dtype=np.int64) % t
delta = q // t
scaled_m = delta * m % q
e1 = gen_normal_poly(size)
e2 = gen_normal_poly(size)
u = gen_binary_poly(size)
ct0 = polyadd(
polyadd(
polymul(pk[0], u, q, poly_mod),
e1, q, poly_mod),
scaled_m, q, poly_mod
)
ct1 = polyadd(
polymul(pk[1], u, q, poly_mod),
e2, q, poly_mod
)
return (ct0, ct1)
4.3 Decryption
因为pk0=[−(a⋅sk+e)]q,pk1=a
,所以我们有(pk1⋅sk≈−pk0)
(e是极小误差项)。在加密过程中:
自然想到构造[ct0+ct1⋅sk]q=δ⋅m+e1+e2⋅sk
,自然就能推导出m
明文。具体的推导过程如下:
带入pk公式,
我们得到了缩放信息与误差项:乘上1/δ,
然后取最近的整数,然后回到环Rt上,
如果想四舍五入到最近的整数得到正确值m,我们就要不受误差项的影响,带有误差的那一项就要小于1/2:
def decrypt(sk, size, q, t, poly_mod, ct):
"""Decrypt a ciphertext——解密一个密文
Args:
sk: secret-key.
size: size of polynomials.
q: ciphertext modulus.
t: plaintext modulus.
poly_mod: polynomial modulus.
ct: ciphertext.
Returns:
Integer representing the plaintext.
"""
scaled_pt = polyadd(
polymul(ct[1], sk, q, poly_mod),
ct[0], q, poly_mod
)
decrypted_poly = np.round(scaled_pt * t / q) % t
return int(decrypted_poly[0])
我们可以很容易地选择参数来保证在简单的加密后得到正确的解密器,但是HE的目标不仅仅是加密/解密数据,还包括对加密数据进行计算(加和乘),计算将扩大错误项,我们将在下面看到,所以真正的问题是在误差项出界之前我们能做多少运算?,计算可以执行的数量将取决于所选择的参数。没有完美的参数,适用于所有情况下,您必须选择根据你的计划,你想要达到的安全性和你想要执行的计算
现在我们知道密钥生成算法输出两个密钥,一个是可以加密消息的公开密钥(蓝色),另一个是可以解密消息的秘密密钥(黑色)。我们知道,加密通过将消息包装成两个主要层来隐藏消息,一层是小错误项(橙色),另一层只能使用秘密密钥打开(黑色)。正如我们在下一节将看到的,对这些加密值的计算将扩大橙色层,而不太小心,它可能会爆炸,使我们的加密值失明,从而使我们无法正确解密。
4.4 Evaluation
接下来讨论如何计算加密的数据,当我们手上有密文时,我们可以对该密文进行加法或乘法,运算的对象可以是其他的密文或明文,我们试着添加明文的操作,这意味着我们将向我们的方案中添加将密文与整数(明文)相加或相乘的功能。
4.4.1 Addition
在加密的过程中,密文ct与原明文m1之间的关系:
当我们想要让密文加上另一个明文m2时,ct0:
我们只需要将新明文m2放缩δ倍即可
解密过程中与之前相比也没有添加新的误差:
我们可以执行无限制次的加法
def add_plain(ct, pt, q, t, poly_mod):
"""Add a ciphertext and a plaintext.
Args:
ct: ciphertext.
pt: integer to add.
q: ciphertext modulus.
t: plaintext modulus.
poly_mod: polynomial modulus.
Returns:
Tuple representing a ciphertext.
"""
size = len(poly_mod) - 1
# encode the integer into a plaintext polynomial
m = np.array([pt] + [0] * (size - 1), dtype=np.int64) % t
delta = q // t
scaled_m = delta * m % q
new_ct0 = polyadd(ct[0], scaled_m, q, poly_mod)
return (new_ct0, ct[1])
4.4.2 Multiplication
ct
是密文,m1
为原明文,我们想要乘上一个明文m2
:
当我们只对ct0
乘上m2
时,发现我们无法解码成功:
−a⋅sk⋅u⋅m2
anda⋅sk⋅u
无法抵消,正确的做法是对ct1
也乘上m2
:
解密,计算误差:
与加法对比,乘法明显的将误差增大了(误差项全乘上一个m2
),意味着乘上一个比较大的密文时将无法解密成功。
def mul_plain(ct, pt, q, t, poly_mod):
"""Multiply a ciphertext and a plaintext.
Args:
ct: ciphertext.
pt: integer to multiply.
q: ciphertext modulus.
t: plaintext modulus.
poly_mod: polynomial modulus.
Returns:
Tuple representing a ciphertext.
"""
size = len(poly_mod) - 1
# encode the integer into a plaintext polynomial
m = np.array([pt] + [0] * (size - 1), dtype=np.int64) % t
new_c0 = polymul(ct[0], m, q, poly_mod)
new_c1 = polymul(ct[1], m, q, poly_mod)
return (new_c0, new_c1)
5 测试代码
# Scheme's parameters
# polynomial modulus degree
n = 2**4
# ciphertext modulus
q = 2**15
# plaintext modulus
t = 2**8
# polynomial modulus
poly_mod = np.array([1] + [0] * (n - 1) + [1])
# Keygen
pk, sk = keygen(n, q, poly_mod)
# Encryption
pt1, pt2 = 73, 20
cst1, cst2 = 7, 5
ct1 = encrypt(pk, n, q, t, poly_mod, pt1)
ct2 = encrypt(pk, n, q, t, poly_mod, pt2)
print("[+] Ciphertext ct1({}):".format(pt1))
print("")
print("\t ct1_0:", ct1[0])
print("\t ct1_1:", ct1[1])
print("")
print("[+] Ciphertext ct2({}):".format(pt2))
print("")
print("\t ct1_0:", ct2[0])
print("\t ct1_1:", ct2[1])
print("")
# Evaluation
ct3 = add_plain(ct1, cst1, q, t, poly_mod)
ct4 = mul_plain(ct2, cst2, q, t, poly_mod)
# Decryption
decrypted_ct3 = decrypt(sk, n, q, t, poly_mod, ct3)
decrypted_ct4 = decrypt(sk, n, q, t, poly_mod, ct4)
print("[+] Decrypted ct3(ct1 + {}): {}".format(cst1, decrypted_ct3))
print("[+] Decrypted ct4(ct2 * {}): {}".format(cst2, decrypted_ct4))
运行结果如下:
可以看到,我们将整数73与20都加密为ct1_0与ct1_1,密文与7,5做同态运算后解密,得到的就是明文之间的运算。