在此记录Tenseal的学习笔记
在张量上进行同态计算的库,是对Seal的python版实现,给开发者提供简单的python接口,无需深究底层密码实现。
当前最新版本:3.11
位置:A library for doing homomorphic encryption operations on tensors
具备以下特点:
环境:MacOS + python3.9
此方法安装出来的是Tenseal的库,是编译好的,是直接拿来用的,但不能源码修改,这种方法对于源码学习者,不建议。
前提:安装pip,也就是需要安装python,这里安装的是3.2版,、
一键安装: python3 pip install tenseal
举例:
(1)新建test.py文件
import tenseal as ts# Setup TenSEAL contextcontext = ts.context( ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60, 40, 40, 60] )context.generate_galois_keys()context.global_scale = 2**40v1 = [0, 1, 2, 3, 4]v2 = [4, 3, 2, 1, 0]# encrypted vectors【编码和加密】enc_v1 = ts.ckks_vector(context, v1) enc_v2 = ts.ckks_vector(context, v2)# 密文+密文result = enc_v1 + enc_v2result.decrypt() # ~ [4, 4, 4, 4, 4]# 点积:<密文,密文>result = enc_v1.dot(enc_v2)print(result.decrypt()) # ~ [10]matrix = [ [73, 0.5, 8], [81, -5, 66], [-100, -78, -2], [0, 9, 17], [69, 11 , 10],]# 密文向量*明文矩阵result = enc_v1.matmul(matrix)print(result.decrypt()) # ~ [157, -90, 153]
(2)执行:python3 test.py
手动cmake安装,适合阅读源码者,这里安装的是最新版:3.11
(1)下载
git clone git://github.com/OpenMined/TenSEAL.git
(2)编译
mkdir buildcmake ..
翻好墙,耐心等待就行!
Tenseal中很多细节都封装了,比如代码中就没有出现密钥生成算法!
同态加密(HE)是一种加密技术,它允许对密文进行计算,并生成解密后与对明文进行相同计算的结果一致。
下面举个例子:
x = 7y = 3x_encrypted = HE.encrypt(x)y_encrypted = HE.encrypt(y)z_encrypted = x_encrypted + y_encrypted# z should now be x + y = 10z = HE.decrypt(z_encrypted)
TenSEALContext对象保存密钥和参数。
(1)下面创建一个TenSEALContext:
import tenseal as tscontext = ts.context(ts.SCHEME_TYPE.BFV, poly_modulus_degree=4096, plain_modulus=1032193)context输出:<tenseal.enc_context.Context object at 0x7fcd0b2e88b0>
需要指定要使用的HE方案(此处为BFV)及其参数。
(2)TenSEALContext现在持有私钥,可以其传递给需要私钥的函数。
public_context = ts.context(ts.SCHEME_TYPE.BFV, poly_modulus_degree=4096, plain_modulus=1032193)print("Is the context private?", ("Yes" if public_context.is_private() else "No"))//私钥为不空返回 Trueprint("Is the context public?", ("Yes" if public_context.is_public() else "No"))//私钥为空返回 Truesk = public_context.secret_key()//暂存私钥# the context will drop the secret-key at this point,删除私钥public_context.make_context_public()print("Secret-key dropped")print("Is the context private?", ("Yes" if public_context.is_private() else "No"))print("Is the context public?", ("Yes" if public_context.is_public() else "No"))输出:Is the context private? YesIs the context public? NoSecret-key droppedIs the context private? NoIs the context public? Yes
(3)TenSEALContext包含的属性很多,因此值得一提的是其他一些有趣的属性。比如用于设置自动重新线性化、重新缩放(仅适用于CKK)和模数切换的属性。这些属性默认启用,如下所示:
print("Automatic relinearization is:", ("on" if context.auto_relin else "off"))print("Automatic rescaling is:", ("on" if context.auto_rescale else "off"))print("Automatic modulus switching is:", ("on" if context.auto_mod_switch else "off"))输出:Automatic relinearization is: onAutomatic rescaling is: onAutomatic modulus switching is: on
(4)TenSEALContext 还提供一个全局默认的scale(在使用CKKS方案时),当用户不提供时,默认使用这个
# this should throw an error as the global_scale isn't defined yettry: print("global_scale:", context.global_scale)except ValueError: print("The global_scale isn't defined yet") # you can define it to 2 ** 20 for instancecontext.global_scale = 2 ** 20print("global_scale:", context.global_scale)输出:The global_scale isn't defined yetglobal_scale: 1048576.0
(1)创建一个加密的整数向量。
plain_vector = [60, 66, 73, 81, 90]encrypted_vector = ts.bfv_vector(context, plain_vector)print("We just encrypted our plaintext vector of size:", encrypted_vector.size())encrypted_vector输出:We just encrypted our plaintext vector of size: 5<tenseal.tensors.bfvvector.BFVVector object at 0x7f8446d27e50>
这里是将一个明文向量加密(编码、加密)为一个BFV密文向量
(2)进行密文加法、减法和乘法。
#密文+明文add_result = encrypted_vector + [1, 2, 3, 4, 5]print(add_result.decrypt())#密文-明文sub_result = encrypted_vector - [1, 2, 3, 4, 5]print(sub_result.decrypt())#密文*明文mul_result = encrypted_vector * [1, 2, 3, 4, 5]print(mul_result.decrypt())#密文+密文encrypted_add = add_result + sub_resultprint(encrypted_add.decrypt())#密文-密文encrypted_sub = encrypted_add - encrypted_vectorprint(encrypted_sub.decrypt())#密文*密文encrypted_mul = encrypted_add * encrypted_subprint(encrypted_mul.decrypt())输出:[60, 66, 73, 81, 90]We just encrypted our plaintext vector of size: 5[61, 68, 76, 85, 95][59, 64, 70, 77, 85][60, 132, 219, 324, 450][120, 132, 146, 162, 180][60, 66, 73, 81, 90][7200, 8712, 10658, 13122, 16200]
(3)c2p比c2c计算快的多
ciphertext to plaintext (c2p) and ciphertext to ciphertext (c2c)
import tenseal as tsfrom time import time# Setup TenSEAL contextcontext = ts.context( ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60, 40, 40, 60] )context.generate_galois_keys()context.global_scale = 2**40v1 = [0, 1111, 2222, 3333, 4444]v2 = [4444, 3333, 2222, 1111, 0]# encrypted vectors【编码和加密】enc_v1 = ts.ckks_vector(context, v1) enc_v2 = ts.ckks_vector(context, v2)t_start = time()_ = enc_v1 * enc_v2 #密文*密文t_end = time()print("c2c multiply time: {} ms".format((t_end - t_start) * 1000))t_start = time()_ = enc_v1 * v2 #密文*明文t_end = time()print("c2p multiply time: {} ms".format((t_end - t_start) * 1000))t_start = time()_ = enc_v1.dot(enc_v2) #<密文,密文>t_end = time()print(_.decrypt())print("<c,c> time: {} ms".format((t_end - t_start) * 1000))t_start = time()_ = enc_v1.dot_(v2) #<密文,明文>t_end = time()print(_.decrypt())print("<c,p> multiply time: {} ms".format((t_end - t_start) * 1000))输出:c2c multiply time: 10.8489990234375 msc2p multiply time: 3.325939178466797 ms[12343211.655333618]<c,c> time: 27.49800682067871 ms[12343211.655338768]<c,p> multiply time: 22.28689193725586 ms
待补充
本节介绍CKKS方案原理及其实现,详细的CKKS解读请参考:
'Part 1, Vanilla Encoding and Decoding'.
'Part 2, Full Encoding and Decoding'.
'Part 3, Encryption and Decryption'.
'Part 4, Multiplication and Relinearization'.
'Part 5, Rescaling'.
中文参考:
CKKS Part1:普通编码和解码
CKKS Part2: CKKS的编码和解码
CKKS Part3: CKKS的加密和解密
CKKS Part4: CKKS的乘法和重线性化
CKKS Part5: CKKS的重缩放
大致方案流程:
(1)缩放因子(scaling factor)
CKKS方案的第一步是将实数向量编码为明文多项式。
缩放因子指的是编码精度,用数字二进制表示。直观地说,我们讨论的是二进制精度,如下图所示:
(2)模多项式的级数(poly_modulus_degree)
即多项式环上的\(Z_q=Z_q[X]/F(X)\),\(F(X)\)的级数\(N\)。
\(N\)产生的影响:
在TenSEAL中,就像在Microsoft SEAL中一样,多项式模的次数必须是2的幂,比如:(1024,2048,4096,8192,16384,32768)
(3)模多项式的系数模数(coefficient modulus sizes)
多项式的系数模数(素数列表),即\(q\)。
\(q\)产生的影响:
在TenSEAL中,就像在Microsoft SEAL中一样,系数模数中的每个素数必须最多为60位,并且必须满足mod 2*poly_modulus_degree=1。
(1)私钥
用于解密,不共享,在TenSEALContext对象中
(2)公钥
用于加密
(3)计算密钥(relinearization keys)
用于重线性化(密钥交换),在乘法后用于降低密文维数。可公开
(4)伽罗瓦密钥(Galois Keys)
用于批处理密文的旋转。可公开
批处理向量的旋转的应用是密文求和
这些操作由TenSEAL自动执行。
(1)重线性化(Relinearization)
该操作在密文乘法后由TenSEAL自动执行,将密文的维数降到2维。若密文的维数维\(K+1\),则计算密钥的维数为\(K-1\)
(2)重缩放(Rescaling)
每次在密文密文或者密文明文后由TenSEAL自动执行。
计算误差随同态乘法次数增多呈指数增长。为了克服这个问题,大多数HE方案通常使用模交换(module switching)技术。CKKS中,使用重缩放,相当于模数切换。可以降低误差。在同态乘法后使用重缩放,误差线性增长,而不是指数增长。
即给定密文的模数为\(q_1,...,q_k\),经过重缩放后,密文模数变为\(q_1,..,q_{k-1}\),所相应的缩小密文中的“明文值”。
此步骤消耗系数模数\(q_1,...,q_k\)中的一个素数。当你消耗掉所有的时候,你将无法执行更多的乘法运算,即Leveled-FHE方案。
import torchfrom torchvision import transformsfrom random import randintimport picklefrom PIL import Imageimport numpy as npfrom matplotlib.pyplot import imshowfrom typing import Dictimport tenseal as ts
首先生成Context:
ctx = ts.context(ts.SCHEME_TYPE.CKKS, 8192, coeff_mod_bit_sizes=[60, 40, 40, 60])
其中:
TenSEAL支持在公钥和对称加密之间切换。默认情况下使用公钥加密。
默认情况下,会自动执行重线性化后和重缩放。通过generate_galois_keys
产生伽罗瓦密钥(Galois Keys)
def context(): context = ts.context(ts.SCHEME_TYPE.CKKS, 8192, coeff_mod_bit_sizes=[60, 40, 40, 60]) context.global_scale = pow(2, 40) context.generate_galois_keys() return contextcontext = context()
张量:可以看成一种数据存储格式
PlainTensor类作为一个转换层,将普通数据类型(例如List,array等)转换为tenseal所支持的明文形式
import numpy as npplain1 = ts.plain_tensor([1,2,3,4], [2,2])print(" First tensor: Shape = {} Data = {}".format(plain1.shape, plain1.tolist()))plain2 = ts.plain_tensor(np.array([5,6,7,8]).reshape(2,2))print(" Second tensor: Shape = {} Data = {}".format(plain2.shape, plain2.tolist()))输出:First tensor: Shape = [2, 2] Data = [[1.0, 2.0], [3.0, 4.0]]Second tensor: Shape = [2, 2] Data = [[5.0, 6.0], [7.0, 8.0]]
从上面可以看出:plain1和plain2就是张量形式,包含数据和形状(shape)
CKKS由于明文空间是浮点数或实数,而计算是在多项式环上,所以加密前需要先编码。
(1)编码
编码分为两步:浮点数 -》实数多项式 -》整数多项式
假设,模多项式的级数为\(N\),那么将\(N/2\)个浮点数编码到明文元素中,然后加密,同态计算就是对密文(多项式)上的系数计算(逐coefficient (一个系数就是一个slot?)),从而实现SIMD操作。整个过程叫做"打包"(batching)
(2)加/解密
加密:对一个明文多项式加密
下面举一个例子:将明文张量(PlainTensor)加密为密文张量(encrypted tensor)
为了创建密文张量,TenSEAL会自动执行编码和加密。这适用于CKKS和BFV方案。
将明文张量(PlainTensor)加密为密文张量(encrypted tensor),存储形式为【密文、shape】
下面有几种密文张量形式:
import tenseal as tsimport numpy as np# Setup TenSEAL contextcontext = ts.context( ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60, 40, 40, 60] )context.generate_galois_keys()context.global_scale = 2**40plain1 = ts.plain_tensor([1,2,3,4], [2,2])print(" First tensor: Shape = {} Data = {}".format(plain1.shape, plain1.tolist()))plain2 = ts.plain_tensor(np.array([5,6,7,8]).reshape(2,2))print(" Second tensor: Shape = {} Data = {}".format(plain2.shape, plain2.tolist()))encrypted_tensor1 = ts.ckks_tensor(context, plain1)encrypted_tensor2 = ts.ckks_tensor(context, plain2)print(" Shape = {}".format(encrypted_tensor1.shape))print(" Encrypted Data = {}.".format(encrypted_tensor1))encrypted_tensor_from_np = ts.ckks_tensor(context, np.array([5,6,7,8]).reshape([2,2]))print(" Shape = {}".format(encrypted_tensor_from_np.shape))输出:First tensor: Shape = [2, 2] Data = [[1.0, 2.0], [3.0, 4.0]]Second tensor: Shape = [2, 2] Data = [[5.0, 6.0], [7.0, 8.0]]Shape = [2, 2]Encrypted Data = <tenseal.tensors.ckkstensor.CKKSTensor object at 0x7f9ddd530400>.Shape = [2, 2]
从上面看出,将普通数据(list:[1,2,3,4])转换为明文张量(plain1),再加密为密文张量(encrypted_tensor1),内部存储【密文数据,shape】
下面是CKKS所支持的密文张量计算:
下面举例:
import tenseal as tsimport numpy as np# Setup TenSEAL contextcontext = ts.context( ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60, 40, 40, 60] )context.generate_galois_keys()context.global_scale = 2**40def decrypt(enc): return enc.decrypt().tolist()plain1 = ts.plain_tensor([1,2,3,4], [2,2])print("First tensor: Shape = {} Data = {}".format(plain1.shape, plain1.tolist()))plain2 = ts.plain_tensor(np.array([5,6,7,8]).reshape(2,2))print("Second tensor: Shape = {} Data = {}".format(plain2.shape, plain2.tolist()))encrypted_tensor1 = ts.ckks_tensor(context, plain1)encrypted_tensor2 = ts.ckks_tensor(context, plain2)#密文(张量)+ 密文(张量)result = encrypted_tensor1 + encrypted_tensor2print("Plain equivalent: {} + {}\nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))#密文(张量)- 密文(张量)result = encrypted_tensor1 - encrypted_tensor2print("Plain equivalent: {} - {}\nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))#密文(张量)* 密文(张量)result = encrypted_tensor1 * encrypted_tensor2print("Plain equivalent: {} * {}\nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))#密文(张量)* 明文(张量)plain = ts.plain_tensor([5,6,7,8], [2,2])result = encrypted_tensor1 * plainprint("Plain equivalent: {} * {}\nDecrypted result: {}.".format(plain1.tolist(), plain.tolist(), decrypt(result)))#取反:密文(张量)result = -encrypted_tensor1 print("Plain equivalent: -{}\nDecrypted result: {}.".format(plain1.tolist(), decrypt(result)))#求幂:密文(张量)^3result = encrypted_tensor1 ** 3print("Plain equivalent: {} ^ 3\nDecrypted result: {}.".format(plain1.tolist(), decrypt(result)))#多项式计算(整数):1 + X^2 + X^3,X是密文(张量)result = encrypted_tensor1.polyval([1,0,1,1])print("X = {}".format(plain1.tolist()))print("1 + X^2 + X^3 = {}.".format(decrypt(result)))#多项式计算(浮点数):1 + X^2 + X^3,X是密文(张量)result = encrypted_tensor1.polyval([0.5, 0.197, 0, -0.004])print("X = {}".format(plain1.tolist()))print("0.5 + 0.197 X - 0.004 x^X = {}.".format(decrypt(result)))输出:First tensor: Shape = [2, 2] Data = [[1.0, 2.0], [3.0, 4.0]]Second tensor: Shape = [2, 2] Data = [[5.0, 6.0], [7.0, 8.0]]Plain equivalent: [[1.0, 2.0], [3.0, 4.0]] + [[5.0, 6.0], [7.0, 8.0]]Decrypted result: [[6.000000000510762, 7.99999999944109], [10.000000000176103, 11.999999999918177]].Plain equivalent: [[1.0, 2.0], [3.0, 4.0]] - [[5.0, 6.0], [7.0, 8.0]]Decrypted result: [[-3.999999998000314, -3.9999999987240265], [-4.0000000013643, -4.0000000013791075]].Plain equivalent: [[1.0, 2.0], [3.0, 4.0]] * [[5.0, 6.0], [7.0, 8.0]]Decrypted result: [[5.000000678675058, 12.000001612431278], [21.000002812898412, 32.000004287986336]].Plain equivalent: [[1.0, 2.0], [3.0, 4.0]] * [[5.0, 6.0], [7.0, 8.0]]Decrypted result: [[5.000000676956037, 12.000001612473657], [21.000002810086173, 32.00000428474004]].Plain equivalent: -[[1.0, 2.0], [3.0, 4.0]]Decrypted result: [[-1.0000000012552241, -2.000000000358531], [-2.9999999994059015, -3.999999999269536]].Plain equivalent: [[1.0, 2.0], [3.0, 4.0]] ^ 3Decrypted result: [[1.0000008094463497, 8.000006439159353], [27.000021714154222, 64.00005146475934]].X = [[1.0, 2.0], [3.0, 4.0]]1 + X^2 + X^3 = [[3.000000945752252, 13.000006978595758], [37.00002291844665, 81.000053606697]].X = [[1.0, 2.0], [3.0, 4.0]]0.5 + 0.197 X - 0.004 x^X = [[0.6930000194866153, 0.8620000226394146], [0.9829999914891329, 1.0319998662943677]].
其中密文张量乘法后需要重线性化:
其中多项式计算(浮点数),来自:Logistic regression over encrypted data from fully homomorphic encryption
下面对MNIST数据集的分类,使用一个卷积和两个完全连接的层以及一个平方激活函数。
它是同态加密的一个重要用例:来自:https://github.com/youben11/encrypted-evaluation
对卷积不了解,后期补充!
下面将提供一些关于如何对同态加密应用程序进行基准测试的提示,并选择最合适的参数。
序列化:通信传输时需要序列化,比如:读写就是序列化
代码和结果:https://github.com/OpenMined/TenSEAL/blob/main/tutorials/Tutorial 3 - Benchmarks.ipynb
结果:
设置的参数不同,会影响密文的序列化
对称或者公钥加密方案实际上并不影响密文的大小,只影响Context的大小。
下面结果是针对堆成加密场景:
【明文数据大小:8.8 KB】
后续补充!