什么是同态加密
同态加密是一种加密形式,但是与我们理解的加密方法不同的是,它允许人们对密文进行特定形式的代数运算得到仍然是加密的结果,然后对结果进行解密得到的明文与直接对明文进行相同的代数运算的结果一样。如下图所示:

换言之,这项技术令人们可以对加密的数据进行处理,得出正确的结果,而在整个处理过程中无需对数据进行解密,直接在加密之后的明文上进行处理即可。
如果一种同态加密算法支持对密文进行任意形式的计算,则称其为全同态加密(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 rr是任意常数。由次可以看出,虽然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的性质不支持乘法同态:![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jMlnOFt-1657808057956)(assets/6.同态加密/20200323182838143.png)]](https://code84.com/wp-content/uploads/2022/10/a4d14d21cee84a46b4997be74f3b397f.png)
安全多方计算
安全多方计算可形式化描述为,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 xx随机的分裂为两个或多个数,如加性秘密分享中,分裂为x 1 x_1x1和x 2 x_2x2 ,使得x = x 1 + x 2 x = x_1+x_2x=x1+x2,分裂后的数分属不同的计算方,谓之分享,各计算方即可根据这些分享的数据展开隐私保护下的算术计算。
Additive Secret Sharing 加法秘密共享
假设现在有两个参与方A和B,他们各自有数据x a x_axa和x b x_bxb,现在C想知道两数的和,但是A和B不想告诉Cx a x_axa和x b x_bxb的值。那么双方利用算术秘密共享可以有: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_2z1+z2既得到两个数的和。
从而完成了加法运算。双方都没有泄露自己的数字。但是前提是,Server之间没有进行共谋攻击,如果两个Server联手,秘密就泄露了。
布尔秘密分享(GMW)
布尔共享与算数秘密共享类似,只是替换了运算。
XOR秘密共享
假设一个秘密值为s ss,此时要将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 = 10s=10,需要拆分成两份,既n=2。然后我们选择一个随机数15。
计算得到r 1 = 15 , r 2 = 5 r_1 = 15, r_2 = 5r1=15,r2=5。然后将r 1 , r 2 r_1,r_2r1,r2分享给P 1 , P 2 P_1, P_2P1,P2。
P 1 P_1P1做运算:r 1 ⊕ 3 = 12 r_1 \oplus 3 = 12r1⊕3=12,P 2 P_2P2做运算:r 2 ⊕ 4 = 1 r_2 \oplus 4 = 1r2⊕4=1。然后P 1 P_1P1和P 2 P_2P2将运算结果还给我们。我们再进行解密:s = 12 ⊕ 1 = 13 s = 12 \oplus 1 = 13s=12⊕1=13,就等价于s ⊕ 3 ⊕ 4 = 13 s \oplus 3 \oplus 4 = 13s⊕3⊕4=13。
这样P 1 P_1P1和P 2 P_2P2既进行了运算,但是又不能得到秘密值。
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*?_na∗b=λ1∗a1∗b1+λ2∗a2∗b2+⋯+λn∗an∗bn
将其抽象简记为:[ ? ∗ ? ] = [ ? ] ∗ [ ? ] [? * ?] = [?] * [?][a∗b]=[a]∗[b]
现在我们的需求是,要求各个参与方协同计算a ∗ b a*ba∗b,显然,如果参与方之间互相交换自己得到的子秘密a i a_iai,就能得到a ∗ b a*ba∗b。但是这样参与者就能通过秘密重构函数得到原始的秘密a , b a,ba,b。所以参与方之间不能直接进行交换,但是还要实现乘法,这就要利用到乘法三元组。
在协议开始之前预先产生一个随机三元组:[ x ] , [ y ] , [ ? ] [x], [y], [?][x],[y],[c],其中x xx和y yy对所有参与者保密,c cc满足c = x ∗ y c=x*yc=x∗y。假设秘密a aa和b bb已经被分享,现在需要计算 a ∗ b a*ba∗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 aa和b bb是加法分享值,所以最终所有参与者能重构出a ∗ b a*ba∗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*ba∗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}_iw=w−α(xiw−yi)xi。
用户对x和y进行加法的秘密分享。
与此同时,一个Server也随机初始化一个初始化参数,然后使用加法的秘密分享分享到两端:
这样双方就共同有了x , y , w \mathbf{x},y,\mathbf{w}x,y,w的秘密分享值。那么两端在进行前向和反向传播的过程中,是没办法直接在本地计算,但是两方也不允许将自己的秘密分享值分享出去,这是MPC的前提。
那么问题来了,如何在本地实现前向传播和反向传播呢?比如计算上面的x ∗ w \mathbf{x}*\mathbf{w}x∗w。因为是加法的秘密分享,并不能直接在本地计算自己本地的两个分享值,这就需要借助其他东西了,也就是Beaver triples(乘法三元组),利用乘法三元组去实现加法秘密分享的乘法运算。
根据乘法三元组,生成两个随机数U , V , Z = U × V U,V,Z = U \times VU,V,Z=U×V,这个三元组就可以用于x ∗ w \mathbf{x}*\mathbf{w}x∗w的计算。
然后还有( 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即可)。那么在训练过程中,两端就要进行下面三种在线的重构:
这三个重构,也就是线性回归中主要的通信开销。
上面模型更新中的减法,因为是加法秘密分享,所以各方直接在本地计算自己秘密分享值得减法即可。
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)