什么是同态加密
同态加密是一种加密形式,但是与我们理解的加密方法不同的是,它允许人们对密文进行特定形式的代数运算得到仍然是加密的结果,然后对结果进行解密得到的明文与直接对明文进行相同的代数运算的结果一样。如下图所示:
换言之,这项技术令人们可以对加密的数据进行处理,得出正确的结果,而在整个处理过程中无需对数据进行解密,直接在加密之后的明文上进行处理即可。
如果一种同态加密算法支持对密文进行任意形式的计算,则称其为全同态加密(Fully Homomorphic Encryption, FHE);如果支持对密文进行部分形式的计算,例如仅支持加法、仅支持乘法或支持有限次加法和乘法,则称其为半同态加密或部分同态加密,英文简称为SWHE(Somewhat Homomorphic Encryption)或PHE(Partially Homomorphic Encryption)。一般而言,由于任意计算均可通过加法和乘法构造,若加密算法同时满足加法同态性和乘法同态性,则可称其满足全同态性。
目前,同态加密算法主要分为半同态加密和全同态加密两大类。半同态加密主要包括以RSA算法和ElGamal算法为代表的乘法同态加密、以Paillier算法为代表的加法同态加密以及以Boneh-Goh-Nissim方案为代表的有限次数全同态加密;全同态加密算法主要包括以Gentry方案为代表的第一代方案、以BGV方案和BFV方案为代表的第二代方案、以GSW方案为代表的第三代方案以及支持浮点数近似计算的CKKS方案等等。
同态加密的组成部分
同态加密算法一般包含以下四个部分:
- KeyGen:密钥生成算法,产生公钥和私钥
- Encryption:加密算法
- Decryption:解密算法
- Homomorphic Property:同态加密计算部分
同态加密的分类
为了更好地理解与运用同态加密算法,我们按照将同态加密算法支持的运算类型和数量,将其分成3类:部分同态加密、层次同态加密、和全同态加密。
- **部分同态加密(Partial HE,简称PHE)指同态加密算法只对加法或乘法(其中一种)有同态的性质。**例如:RSA加密是最早应用的公钥加密算法框架,同时RSA算法也是一种PHE算法,其对乘法有同态的性质。PHE的研究成果出现比较早,并且加法同态加密算法(Additive HE)比 乘法同态加密算法要多一些。PHE的优点是原理简单、易实现,缺点是仅支持一种运算(加法或乘法)。
- **层次同态加密算法(LHE,Leveled HE 或 SWHE ,SomeWhat HE)一般支持有限次数的加法和乘法运算。**层次同态加密的研究主要分为两个阶段,第一个阶段是在2009年Gentry提出第一个FHE框架以前,比较著名的例子有:BGN算法、姚氏混淆电路等;第二个阶段在Gentry FHE框架之后,主要针对FHE效率低的问题。LHE的优点是同时支持加法和乘法,并且因为出现时间比PHE晚,所以技术更加成熟、一般效率比FHE要高很多、和PHE效率接近或高于PHE,缺点是支持的计算次数有限。
- 全同态加密算法(Fully HE,简称FHE)支持在密文上进行无限次数的、任意类型的计算。从使用的技术上分,FHE有以下类别:基于理想格的FHE方案、基于LWE/RLWE的FHE方案等等。FHE的优点是支持的算子多并且运算次数没有限制,缺点是效率很低,目前还无法支撑大规模的计算。
同态加密在联邦学习中的运用
在联邦学习中,各个参与方分享梯度到服务服务器上以聚合全局模型,但是已有研究证明通过几个梯度信息就可以推断出原数据信息,所以如果直接发送明文的结果可能会有隐私泄露风险。隐私风险可能来自于中间人攻击,又或者参与方认为服务器方都是不可信的。在这种场景下,同态加密就可以发挥很重要的作用。
多方直接将梯度信息采用同态加密算法进行加密,然后交给服务器进行聚合运算,此时因为数据是被加密的,服务器也只能进行聚合运算,但是不能获取到任何梯度信息,避免了隐私泄露。
这种情况下,联邦学习中的有三类参与者,一类是加入训练的参与者,一类是server,在一类是CA机构。由CA机构生成同态加密的密钥对交给参与者对梯度进行加密,然后加密梯度交给server,server计算完成之后交给CA机构进行解密,然后再由CA将聚合后的结果分发给参与者。
Paiilier库
Paiilier是一种同态加密算法,满足加法同态。其支持的运算如下:
- [ [ x 1 ] ] p k ∗ [ [ x 2 ] ] p k = [ [ x 1 + x 2 ] ] p k [[x_1]]_{pk}*[[x_2]]_{pk} = [[x_1 + x_2]]_{pk} [[x1]]pk∗[[x2]]pk=[[x1+x2]]pk,由这个可以看出,即使是加密之后的密文,也支持加法运算,这也就是Paiilier是加法同态的原因
- [ [ x 1 ] ] p k r = [ [ r ∗ x 1 ] ] p k [[x_1]]^r_{pk} = [[r*x_1]]_{pk} [[x1]]pkr=[[r∗x1]]pk,其中 r r r是任意常数。由次可以看出,虽然Paiilier不支持密文之间的乘法,但是支持密文与常量之间的乘法。
- [ [ x 1 ] ] p k + r = [ [ x 1 + r ] ] p k [[x_1]]_{pk}+r = [[x_1+r]]_{pk} [[x1]]pk+r=[[x1+r]]pk,由此可以看出Paiilier不仅支持密文与常量的乘法,还支持密文与常量的加减法。
总结起来就是,Paiilier支持密文之间的加减法,支持密文与明文之间的乘除法。
Paiilier库实现了半同态加密Paiilier算法,可以简单实现同态加密。
项目官方地址:https://github.com/data61/python-paillier
安装
去PyPI单独看一下phe的安装说明:phe(插一句,PyPI真的是个非常好用的和python相关的各种包的安装集合地)
直接进行安装:
pip install phe
在安装phe之前它推荐安装gmpy2,虽然不是必须的,但gmpy2做大数计算和精确计算以及提高计算效率都很有帮助。
同样到pypi中查看安装帮助:https://pypi.org/project/gmpy2/#files
我们也可以直接安装:
pip install gmpy2
测试
from phe import paillier
import gmpy2 as gy
public_key, private_key = paillier.generate_paillier_keypair()
def test_mul(encrypt_x, encrypt_y, origin_x, origin_y):
# [[x]]*[[y]] = [[x+y]]
encrypt_res = paillier.EncryptedNumber(public_key, encrypt_x.ciphertext() * encrypt_y.ciphertext())
res = origin_x + origin_y
assert res == private_key.decrypt(encrypt_res)
def test_exponent(encrypt_x, x, r):
# [[x]]^r = [[x*r]]
encrypt_res = paillier.EncryptedNumber(public_key, encrypt_x.ciphertext() ** r)
res = x * r
assert res == private_key.decrypt(encrypt_res)
secret_number_list = [3, 4, 5]
encrypted_number_list = [public_key.encrypt(x) for x in secret_number_list]
test_mul(encrypted_number_list[0], encrypted_number_list[1], secret_number_list[0], secret_number_list[1])
test_exponent(encrypted_number_list[0], secret_number_list[0], 3)
encrypt_res = encrypted_number_list[0] + encrypted_number_list[1]
assert 7 == private_key.decrypt(encrypt_res)
encrypt_res = encrypted_number_list[0] + 4
assert 7 == private_key.decrypt(encrypt_res)
再通过计算测试一下同态性,可以发现phe中的paillier支持同态相加以及密文与明文的加法和乘法计算,同时也验证了paillier的性质不支持乘法同态:
安全多方计算
安全多方计算可形式化描述为,n个计算参与方分别持有数据x1,x2,…,xn,协议的目的是各方利用自己和的它方的秘密数据计算一个预先达成的共识函数y1,y2,…yn=f(x1,x2,…,xn),此时任意一方可以得到对应的自己想要的结果yi,但无法获得其他任何信息,无法得到其他人的密码信息。如下图:
在传统分布式计算模型下,传统的分布式计算由中心节点协调各用户的计算进程,收集各参与方的明文输入信息,各参与方的原始数据对第三方来说毫无秘密可言,很容易造成数据泄露。在MPC计算模式下,不需要可信第三方收集所有参与节点的原始明文数据,只需要各参与节点之间相互交换数据即可,而且交换的是处理后(如同态加密、秘密共享等处理方法)的数据,保证其他参与节点拿到数据后,也无法反推原始明文数据,确保了各参与方数据的私密性。
多方安全计算的特点:
-
两方或者多方参与基于他们各自隐私或秘密数据输入的计算。
-
参与一方都不愿意让其他任何第三方知道自己的输入信息。
MPC方案主要可分为基于混淆电路(Garbled Circuit,GC)和基于秘密共享两种。
根据参与方的个数,可分为两方安全计算(two party computation,简称2PC)
和多方安全计算(multiparty computation)
。
秘密共享
传统的对称加密就可以视为一种密码共享,例如: C = E n c k e y ( M ) C = Enc_{key}(M) C=Enckey(M)。其中的M是明文,C是密文,key是密钥。也就是说,明文被分解成了密文和密钥,分发给多方就实现了秘密共享。
谈及秘密分享时,一般取的是它狭义的定义,即数据的裂片被一组参与方分别拥有。这些参与方一般是对等的角色,且其中的分享或者说分裂和还原均不涉及加解密。秘密分享的优势正在于此,数据的分裂和还原运算一般来说成本极低,如加性秘密分享(Additive Secret Sharing),分裂和还原只是加减法,除此之外,就是对分裂点的选择需要做一次随机数生成,这些运算成本远低于一般的加解密运算。
如果仅限于分裂和还原,秘密分享就没有什么价值,它的真正价值在于处于分裂态可以运算,而运算的结果在多方参与的情况下可以还原。这正是隐私计算中所追求的可用而不可见的性质,因此秘密分享成为隐私计算中最重要的框架之一的多方安全计算的基石。
算数秘密共享
算术秘密分享( Arithmetic Secret Sharing ) 是一种低成本的秘密分享,它的主要思想是将一个数 x x x随机的分裂为两个或多个数,如加性秘密分享中,分裂为 x 1 x_1 x1和 x 2 x_2 x2 ,使得 x = x 1 + x 2 x = x_1+x_2 x=x1+x2,分裂后的数分属不同的计算方,谓之分享,各计算方即可根据这些分享的数据展开隐私保护下的算术计算。
Additive Secret Sharing 加法秘密共享
假设现在有两个参与方A和B,他们各自有数据
x
a
x_a
xa和
x
b
x_b
xb,现在C想知道两数的和,但是A和B不想告诉C
x
a
x_a
xa和
x
b
x_b
xb的值。那么双方利用算术秘密共享可以有:A:
x
a
=
x
a
1
+
x
a
2
x_a = x_{a1}+x_{a2}
xa=xa1+xa2,B:
x
b
=
x
b
1
+
x
b
2
x_b = x_{b1}+x_{b2}
xb=xb1+xb2。
A此时将
x
a
1
x_{a1}
xa1发送给Server1,将
x
a
2
x_{a2}
xa2发送给Server2,B同理。
这时候C想知道和,C向各个Server请求和计算算子,Server1计算
z
1
=
x
a
1
+
x
b
1
z_1 = x_{a1} + x_{b1}
z1=xa1+xb1,Server2计算
z
2
=
x
a
2
+
x
b
2
z_2 = x_{a2} + x_{b2}
z2=xa2+xb2,最终C计算
z
1
+
z
2
z_1+z_2
z1+z2既得到两个数的和。
从而完成了加法运算。双方都没有泄露自己的数字。但是前提是,Server之间没有进行共谋攻击,如果两个Server联手,秘密就泄露了。
布尔秘密分享(GMW)
布尔共享与算数秘密共享类似,只是替换了运算。
XOR秘密共享
假设一个秘密值为
s
s
s,此时要将s分解成n份,那么就选择n-1个同bit长度的随机数。
r
1
,
r
2
,
…
,
r
n
−
1
r_1,r_2,\dots,r_{n-1}
r1,r2,…,rn−1,计算:
r
n
=
s
⊕
r
1
⊕
r
2
⊕
⋯
⊕
r
n
−
1
r_n = s \oplus r_1 \oplus r_2 \oplus \dots \oplus r_{n-1}
rn=s⊕r1⊕r2⊕⋯⊕rn−1
最终s就被拆分成了
r
1
,
r
2
,
…
,
r
n
r_1,r_2,\dots,r_{n}
r1,r2,…,rn,既秘密的分享。恢复秘密只需要计算:
s
=
r
1
⊕
r
2
⊕
⋯
⊕
r
n
s= r_1 \oplus r_2 \oplus \dots \oplus r_{n}
s=r1⊕r2⊕⋯⊕rn
举例
我们假设现在有秘密值
s
=
10
s = 10
s=10,需要拆分成两份,既n=2。然后我们选择一个随机数15。
计算得到
r
1
=
15
,
r
2
=
5
r_1 = 15, r_2 = 5
r1=15,r2=5。然后将
r
1
,
r
2
r_1,r_2
r1,r2分享给
P
1
,
P
2
P_1, P_2
P1,P2。
P
1
P_1
P1做运算:
r
1
⊕
3
=
12
r_1 \oplus 3 = 12
r1⊕3=12,
P
2
P_2
P2做运算:
r
2
⊕
4
=
1
r_2 \oplus 4 = 1
r2⊕4=1。然后
P
1
P_1
P1和
P
2
P_2
P2将运算结果还给我们。我们再进行解密:
s
=
12
⊕
1
=
13
s = 12 \oplus 1 = 13
s=12⊕1=13,就等价于
s
⊕
3
⊕
4
=
13
s \oplus 3 \oplus 4 = 13
s⊕3⊕4=13。
这样
P
1
P_1
P1和
P
2
P_2
P2既进行了运算,但是又不能得到秘密值。
xor秘密共享共享出去的秘密仅支持xor运算,既对xor是同态的。
Shamir秘密共享
Beaver Triples 三元组
Beaver三元组:主要用于安全多方计算协议中的乘法计算。应用范围为加法和乘法均为线性的秘密分享机制。
我们用用
[
∗
]
[∗]
[∗]表示秘密被分享之后的状态,如
[
𝑎
]
[𝑎]
[a]表示秘密𝑎已经通过秘密分享函数被分享给了参与者,如果有n个参与者,则
[
𝑎
]
=
𝑎
1
,
𝑎
2
,
…
,
𝑎
𝑛
[𝑎] = {𝑎_1,𝑎_2,…,𝑎_𝑛}
[a]=a1,a2,…,an。假设秘密共享函数为𝑓(𝑥),𝑥为待共享的秘密,参与者为
𝑃
1
,
𝑃
2
,
…
,
𝑃
𝑛
𝑃_1,𝑃_2,…,𝑃_𝑛
P1,P2,…,Pn,参与者
𝑃
𝑖
𝑃_𝑖
Pi拿到的
𝑥
𝑥
x的子秘密记为
𝑥
𝑖
𝑥_𝑖
xi。
假设需要分享的秘密为
𝑎
𝑎
a、
𝑏
𝑏
b,参与者
𝑃
𝑖
𝑃_𝑖
Pi获得的
𝑎
𝑎
a、
𝑏
𝑏
b的子秘密为
𝑎
𝑖
𝑎_𝑖
ai、
𝑏
𝑖
𝑏_𝑖
bi。
加法秘密分享机制为线性的意思为:存在一组常数
λ
1
,
λ
2
,
…
,
λ
𝑛
\lambda_1,\lambda_2,\dots,\lambda_𝑛
λ1,λ2,…,λn,使得:
𝑎
+
𝑏
=
λ
1
∗
(
𝑎
1
+
𝑏
1
)
+
λ
2
∗
(
𝑎
2
+
𝑏
2
)
+
⋯
+
λ
n
∗
(
𝑎
n
+
𝑏
n
)
𝑎+𝑏 = \lambda_1* (𝑎_1+𝑏_1) + \lambda_2*(𝑎_2 + 𝑏_2) + ⋯+ \lambda_n*(𝑎_n+𝑏_n)
a+b=λ1∗(a1+b1)+λ2∗(a2+b2)+⋯+λn∗(an+bn)
将其抽象简记为:
[
𝑎
+
𝑏
]
=
[
𝑎
]
+
[
𝑏
]
[𝑎 + 𝑏] = [𝑎] + [𝑏]
[a+b]=[a]+[b]
乘法秘密分享机制为线性的意思为:存在一组常数
λ
1
,
λ
2
,
…
,
λ
𝑛
\lambda_1,\lambda_2,\dots,\lambda_𝑛
λ1,λ2,…,λn使得:
𝑎
∗
𝑏
=
λ
1
∗
𝑎
1
∗
𝑏
1
+
λ
2
∗
𝑎
2
∗
𝑏
2
+
⋯
+
λ
n
∗
𝑎
n
∗
𝑏
n
𝑎*𝑏 = \lambda_1*𝑎_1*𝑏_1+ \lambda_2*𝑎_2*𝑏_2+ \dots+\lambda_n*𝑎_n*𝑏_n
a∗b=λ1∗a1∗b1+λ2∗a2∗b2+⋯+λn∗an∗bn
将其抽象简记为:
[
𝑎
∗
𝑏
]
=
[
𝑎
]
∗
[
𝑏
]
[𝑎 * 𝑏] = [𝑎] * [𝑏]
[a∗b]=[a]∗[b]
现在我们的需求是,要求各个参与方协同计算
a
∗
b
a*b
a∗b,显然,如果参与方之间互相交换自己得到的子秘密
a
i
a_i
ai,就能得到
a
∗
b
a*b
a∗b。但是这样参与者就能通过秘密重构函数得到原始的秘密
a
,
b
a,b
a,b。所以参与方之间不能直接进行交换,但是还要实现乘法,这就要利用到乘法三元组。
在协议开始之前预先产生一个随机三元组:
[
x
]
,
[
y
]
,
[
𝑐
]
[x], [y], [𝑐]
[x],[y],[c],其中
x
x
x和
y
y
y对所有参与者保密,
c
c
c满足
c
=
x
∗
y
c=x*y
c=x∗y。假设秘密
a
a
a和
b
b
b已经被分享,现在需要计算
a
∗
b
a*b
a∗b 。
步骤如下:
- 所有参与者公开自己计算的 [ α ] [\alpha] [α],其在本地计算方式为: [ α ] = [ a ] − [ x ] [\alpha]=[a]−[x] [α]=[a]−[x]
- 所有参与者公开自己计算的 [ β ] [\beta] [β],其在本地计算方式为: [ β ] = [ b ] − [ y ] [\beta] = [b]−[y] [β]=[b]−[y]
- 所有参与者计算 [ 𝑧 ] = [ c ] + [ α ] ∗ [ y ] + [ β ] ∗ [ x ] + [ α ] ∗ [ β ] [𝑧]=[c]+[\alpha]*[y]+[\beta]*[x]+[\alpha]*[\beta] [z]=[c]+[α]∗[y]+[β]∗[x]+[α]∗[β]
- 最终 [ a ] ∗ [ b ] = [ z ] [a]*[b] = [z] [a]∗[b]=[z] ,又因为 a a a和 b b b是加法分享值,所以最终所有参与者能重构出 a ∗ b a*b a∗b
因为
[
α
]
[\alpha]
[α] 和
[
β
]
[\beta]
[β]已公开,因此所有参与者都可以通过秘密重构函数独立计算出
α
\alpha
α和
β
\beta
β。又因为:
[
𝑧
]
=
(
[
α
]
+
[
x
]
)
×
(
[
β
]
+
[
y
]
)
=
[
α
]
∗
[
β
]
+
[
α
]
∗
[
y
]
+
[
x
]
∗
[
β
]
+
[
x
]
∗
[
y
]
=
[
c
]
+
[
α
]
∗
[
y
]
+
[
β
]
∗
[
x
]
+
[
α
]
∗
[
β
]
=
[
c
]
+
(
[
a
]
−
[
x
]
)
∗
[
y
]
+
(
[
b
]
−
[
y
]
)
∗
[
x
]
+
(
[
a
]
−
[
x
]
)
∗
(
[
b
]
−
[
y
]
)
=
[
c
]
+
[
a
]
∗
[
y
]
−
[
x
]
∗
[
y
]
+
[
b
]
∗
[
x
]
−
[
y
]
∗
[
x
]
+
[
a
]
∗
[
b
]
+
[
x
]
∗
[
y
]
−
[
y
]
∗
[
a
]
−
[
x
]
∗
[
b
]
=
[
a
]
∗
[
b
]
+
[
c
]
\begin{align} [𝑧] & = ([\alpha]+[x]) \times ([\beta]+[y]) \\ & = [\alpha]*[\beta] + [\alpha]*[y] + [x]*[\beta] + [x]*[y] \\ & =[c]+[\alpha]*[y]+[\beta]*[x]+[\alpha]*[\beta] \\ &= [c]+ ([a] − [x]) * [y] + ([b] − [y]) * [x] + ([a] − [x]) * ([b] − [y]) \\ &= [c]+ [a] * [y] − [x] * [y] + [b] * [x] − [y] * [x] + [a] * [b] + [x] * [y] − [y] * [a] − [x] * [b] \\ &= [a] * [b] + [c] \end{align}
[z]=([α]+[x])×([β]+[y])=[α]∗[β]+[α]∗[y]+[x]∗[β]+[x]∗[y]=[c]+[α]∗[y]+[β]∗[x]+[α]∗[β]=[c]+([a]−[x])∗[y]+([b]−[y])∗[x]+([a]−[x])∗([b]−[y])=[c]+[a]∗[y]−[x]∗[y]+[b]∗[x]−[y]∗[x]+[a]∗[b]+[x]∗[y]−[y]∗[a]−[x]∗[b]=[a]∗[b]+[c]
所有参与者成功计算得到
a
∗
b
a*b
a∗b。
安全多方计算线性回归实例
Two-server Model
假设现在有一个用户,它有很多数据,需要训练一个模型,但是它自己没有能力训练,需要大公司的帮助,但是它又不想把数据直接给某一个大公司,因为这样导致自己的隐私泄露,这时候就可以利用Two-server model。用户将自己的数据分成两个分享值,将分享值发送给两个服务器。单个服务器得到的是数据的分片,不能还原出完整的信息(假设两个服务器之间不会进行共谋攻击)
。
然后两个服务器在基于分片的基础上进行两方的安全计算,最终得到机器学习模型。
Linear Regression
假设我们现在的回归函数为:
y
=
w
x
y = \mathbf{w}\mathbf{x}
y=wx.
所以有模型更新:
w
=
w
−
α
(
x
i
w
−
y
i
)
x
i
\mathbf{w} = \mathbf{w} - \alpha(\mathbf{x}_i\mathbf{w} - y_i)\mathbf{x}_i
w=w−α(xiw−yi)xi。
用户对x和y进行加法的秘密分享。
与此同时,一个Server也随机初始化一个初始化参数,然后使用加法的秘密分享分享到两端:
这样双方就共同有了
x
,
y
,
w
\mathbf{x},y,\mathbf{w}
x,y,w的秘密分享值。那么两端在进行前向和反向传播的过程中,是没办法直接在本地计算。因为双方现在拥有的
x
,
y
,
w
\mathbf{x},y,\mathbf{w}
x,y,w都是处于分享态的,我们不能hi姐将分享态的
<
x
>
1
<x>_1
<x>1和
<
w
>
1
<w>_1
<w>1进行计算。如果直接在分享态进行运算,然后再将结果结果进行相加,我们通过简单的式子就知道这是错误的。但是两方也不允许将自己的秘密分享值分享出去,这是MPC的前提。
那么问题来了,如何在本地实现前向传播和反向传播呢?既如何实现在分享态的情况下,双方也能正确计算得到
x
w
xw
xw呢?
这就需要借助其他东西了,也就是Beaver triples(乘法三元组)
,利用乘法三元组去实现加法秘密分享的乘法运算。
根据乘法三元组,生成两个随机数
U
,
V
,
Z
=
U
×
V
U,V,Z = U \times V
U,V,Z=U×V,这个三元组就可以用于
x
∗
w
\mathbf{x}*\mathbf{w}
x∗w的计算。
通过这种方式计算之后,双方就各自拥有了
x
w
xw
xw的密钥分享,也就是S1有了
<
x
w
>
1
<xw>_1
<xw>1,S2有了
<
x
w
>
2
<xw>_2
<xw>2。
然后还有
(
x
∗
w
−
y
)
×
x
(\mathbf{x}*\mathbf{w}-y)\times \mathbf{x}
(x∗w−y)×x,也需要一次乘法运算,所以我们还需要一个三元组
U
,
V
′
,
Z
′
=
U
×
V
′
U,V',Z' = U \times V'
U,V′,Z′=U×V′(因为第二个乘法的乘数还是原来的X,所以U可以不变,重新生成一个新的V即可)
。
最终双方都更新了自己的模型分享值,将双方的模型分享值相加,就得到了最终的模型。
那么在训练过程中,两端就要进行下面三种在线的重构:
这三个重构,也就是线性回归中主要的通信开销。这个通信开销主要是由beaver triples乘法运算所引发的。
上面模型更新中的减法,因为是加法秘密分享,所以各方直接在本地计算自己秘密分享值得减法即可。
被分享的十进制数上的算数操作
前人工作的不高效主要来源于在共享/加密的十进制数上的计算,之前的解决方案主要有两种:
1、把共享的十进制数看成整数,比如保留8位小数,就将原来的浮点数乘
1
0
8
10^8
108,转换为整数。做乘法后在很大的有限域上完全保留下所有的精度。缺点是只能支持一定数量的乘法,因为结果的范围会随着乘法的数量指数级增长。《Applying neural networks to encrypted data with high throughput and accuracy》
2、在十进制数上利用安全两方计算的布尔电路来做定点或者浮点乘法。这带来了大量的负载,因为计算两个l-比特长的数的乘法就需要O(l^2)个门,在2PC(例如姚氏混淆电路中),这样的电路需要为每个乘法进都算一次。《Secure linear regression on vertically partitioned datasets.》
本文提出了一种简单但有效的支持有限域上的十进制数的解决方案。将十进制数转换为定点数,考虑对两个小数部分最多有lD比特的两个十进制数x y的定点乘法,首先把通过
x
′
=
2
l
D
×
x
,
y
′
=
2
l
D
×
y
x' = 2^{l_D}×x,y' = 2^{l_D}×y
x′=2lD×x,y′=2lD×y,把数字转化成整数,然后把他们相乘得到
z
=
x
′
×
y
′
z = x'×y'
z=x′×y′,注意到
z
z
z最多有
2
l
D
2^{l_D}
2lD表示积的小数部分,所以我们简单粗暴地将最后
z
z
z的最后
l
D
l_D
lD比特砍掉,这样它最多有
l
D
l_D
lD比特来表示小数部分。
z
z
z也可以被秘密分享,可以证明,两个服务器分别砍去自己的最后
l
D
l_D
lD位比特,和想要的
z
z
z其实最多相差一个最不重要的比特位,这对最后结果的影响可以说是微乎其微。换句话说,相较于标准的定点乘,我们只是缺少了小数部分最不重要的一个比特位。
secret_sharing.py
import random
from collections.abc import Iterable
import numpy as np
class AdditiveSecretSharing:
def __init__(self, data, n):
# 分成几个秘密分享值
self.n = n
self.origin_data = data
self.sharing_values = self.split(data)
def get_sharing_value(self, index):
return self.sharing_values[index]
def split(self, data):
if isinstance(data, Iterable) or type(data) == np.ndarray:
res = {}
for index, d in enumerate(data):
res["sharing_{}".format(index)] = self.__split(d)
return res
return self.__split(data)
def sum(self):
if isinstance(self.sharing_values, dict):
res = {}
for k, v in self.sharing_values:
res[k] = np.sum(v).item()
return sum(self.sharing_values)
def __split(self, data):
# count = self.n
res_list = [data]
while True:
if len(res_list) * 2 > self.n:
break
res = []
for data in res_list:
res.extend(self.__two_split(data))
res_list = res
random.shuffle(res_list)
re_split_list = res_list[0:self.n - len(res_list)]
res_list = res_list[self.n - len(res_list):]
for data in re_split_list:
res_list.extend(self.__two_split(data))
return np.array(res_list)
def __two_split(self, data):
# random_num = round(random.random(), 2)
random_num = 0.5
value = round(data * random_num, 4)
return [data - value, value]
def __str__(self):
return str(self.sharing_values)
class BeaverTriple:
@staticmethod
def multi(u: AdditiveSecretSharing, v: AdditiveSecretSharing, z: AdditiveSecretSharing,
data_0: AdditiveSecretSharing,
data_1: AdditiveSecretSharing):
alpha = data_0.sum() - u.sum()
beta = data_1.sum() - v.sum()
res = 0
for index in range(len(v.sharing_values)):
res += z.sharing_values[index] + alpha * v.sharing_values[index] + beta * \
u.sharing_values[index]
res += alpha * beta
return res
if __name__ == '__main__':
num_participants = 2
x = AdditiveSecretSharing(1320, num_participants)
w = AdditiveSecretSharing(12320, num_participants)
print(x, w)
u = AdditiveSecretSharing(1, num_participants)
v = AdditiveSecretSharing(1, num_participants)
z = AdditiveSecretSharing(u.sum() * v.sum(), num_participants)
print(u, v)
multi = BeaverTriple.multi(u, v, z, x, w)
print(multi)