• 欢迎光临~

# Tenseal库

## 介绍

• BFV方案的加解密（整数）
• CKKS方案的加解密（浮点数）
• 密文-密文、密文-明文的加法和乘法运算（同态计算）
• 点积和矩阵乘法
• 将Seal封装为tenseal.sealapi

### 安装

#### pip安装

（1）新建test.py文件

``````import tenseal as ts

# Setup TenSEAL context
context = 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**40

v1 = [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_v2
result.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 安装

（1）下载

``````git clone git://github.com/OpenMined/TenSEAL.git
``````

（2）编译

``````mkdir build
cmake ..
``````

## 开始

Tenseal中很多细节都封装了，比如代码中就没有出现密钥生成算法！

### 同态加密

``````x = 7
y = 3

x_encrypted = HE.encrypt(x)
y_encrypted = HE.encrypt(y)

z_encrypted = x_encrypted + y_encrypted

# z should now be x + y = 10
z = HE.decrypt(z_encrypted)
``````

### TenSEALContext对象

TenSEALContext对象保存密钥和参数。
（1）下面创建一个TenSEALContext：

``````import tenseal as ts
context = ts.context(ts.SCHEME_TYPE.BFV, poly_modulus_degree=4096, plain_modulus=1032193)
context

``````

（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"))//私钥为不空返回 True
print("Is the context public?", ("Yes" if public_context.is_public() else "No"))//私钥为空返回 True

sk = 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? Yes
Is the context public? No
Secret-key dropped
Is the context private? No
Is 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: on
Automatic rescaling is: on
Automatic modulus switching is: on
``````

（4）TenSEALContext 还提供一个全局默认的scale（在使用CKKS方案时），当用户不提供时，默认使用这个

``````# this should throw an error as the global_scale isn't defined yet
try:
print("global_scale:", context.global_scale)
except ValueError:
print("The global_scale isn't defined yet")

# you can define it to 2 ** 20 for instance
context.global_scale = 2 ** 20
print("global_scale:", context.global_scale)

The global_scale isn't defined yet
global_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>
``````

（2）进行密文加法、减法和乘法。

``````#密文+明文
add_result = encrypted_vector + [1, 2, 3, 4, 5]
#密文-明文
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())
#密文+密文
#密文-密文
print(encrypted_sub.decrypt())
#密文*密文
print(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 ts
from time import time

# Setup TenSEAL context
context = 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**40

v1 = [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 ms
c2p multiply time: 3.325939178466797 ms
[12343211.655333618]
<c,c>  time: 27.49800682067871 ms
[12343211.655338768]
<c,p> multiply time: 22.28689193725586 ms
``````

## 近似计算（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原理

CKKS Part1：普通编码和解码
CKKS Part2: CKKS的编码和解码
CKKS Part3: CKKS的加密和解密
CKKS Part4: CKKS的乘法和重线性化
CKKS Part5: CKKS的重缩放

#### 参数

（1）缩放因子（scaling factor）
CKKS方案的第一步是将实数向量编码为明文多项式。

（2）模多项式的级数（poly_modulus_degree）

(N)产生的影响：

• 明文多项式的系数个数
• 密文元素的大小
• 方案的计算性能（越大越差）
• 安全级别（越大越好）

（3）模多项式的系数模数（coefficient modulus sizes）

(q)产生的影响：

• 密文元素的大小
• 方案的安全级数(L)，即乘法次数
• 安全级别（越大越好）

#### 密钥

（1）私钥

（2）公钥

（3）计算密钥（relinearization keys）

（4）伽罗瓦密钥（Galois Keys）

#### 内部计算

（1）重线性化（Relinearization）

（2）重缩放（Rescaling）

### 使用

#### 引入

``````import torch
from torchvision import transforms
from random import randint
import pickle
from PIL import Image
import numpy as np
from matplotlib.pyplot import imshow
from typing import Dict

import tenseal as ts
``````

#### Context

``````ctx = ts.context(ts.SCHEME_TYPE.CKKS, 8192, coeff_mod_bit_sizes=[60, 40, 40, 60])
``````

• 方案类型：ts.SCHEME_TYPE.CKKS
• poly_modulus_degree：8192
• coeff_mod_bit_sizes：系数模数大小，这里的[60, 40, 40, 60]表示系数模数将包含4个素数，分别为60位、40位、40位和60位。
• global_scale：缩放因子（scaling factor），即(2^{40})

TenSEAL支持在公钥和对称加密之间切换。默认情况下使用公钥加密。

``````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 context

context = context()
``````

#### 明文张量（PlainTensor）

PlainTensor类作为一个转换层，将普通数据类型（例如List，array等）转换为tenseal所支持的明文形式

``````import numpy as np

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()))

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]]
``````

#### 加密

CKKS由于明文空间是浮点数或实数，而计算是在多项式环上，所以加密前需要先编码。
（1）编码

（2）加/解密

• BFVVector：1D（1维）整数数组
• CKKSVector：1D（1维）浮点数数组

``````import tenseal as ts
import numpy as np

# Setup TenSEAL context
context = 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**40

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)

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]
``````

#### 同态计算

``````import tenseal as ts
import numpy as np

# Setup TenSEAL context
context = 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**40

def 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_tensor2
print("Plain equivalent: {} + {}nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))

#密文（张量）- 密文（张量）
result = encrypted_tensor1 - encrypted_tensor2
print("Plain equivalent: {} - {}nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))

#密文（张量）* 密文（张量）
result = encrypted_tensor1 * encrypted_tensor2
print("Plain equivalent: {} * {}nDecrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))

#密文（张量）* 明文（张量）
plain = ts.plain_tensor([5,6,7,8], [2,2])
result = encrypted_tensor1 * plain
print("Plain equivalent: {} * {}nDecrypted result: {}.".format(plain1.tolist(), plain.tolist(), decrypt(result)))

#取反：密文（张量）
result = -encrypted_tensor1
print("Plain equivalent: -{}nDecrypted result: {}.".format(plain1.tolist(), decrypt(result)))

#求幂：密文（张量）^3
result = encrypted_tensor1 ** 3
print("Plain equivalent: {} ^ 3nDecrypted 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]] ^ 3
Decrypted 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]].
``````

## 性能测试

#### Context 序列化

• 对称加密方案创建的Context比公钥加密方案创建的Context更小。
• 减少系数模数（coefficient modulus）的长度会减少Context的大小，但也会减少可用乘法的深度(L)，也会影响精度（对于CKKS）。
• Galois密钥只会增加公共Context的大小（没有私钥）。仅当需要执行密文旋转时发送它们。
• 重新线性密钥只会增加公共Context的大小。仅当需要执行密文乘法时才发送它们。
• 当我们发送私钥时，可以重新生成重新线性化/伽罗瓦密钥，而无需发送它们。

#### 密文（Ciphertext）序列化

【明文数据大小：8.8 KB】

• 多项式模(N)的增加导致密文的增加。
• 系数模数（coefficient modulus）的长度影响密文大小。
• 系数模数大小的值会影响密文大小以及精度。
• 对于一组固定的多项式模数(N)和系数模数，更改精度不会影响密文大小。