## 介绍

BFV方案的加解密（整数）
CKKS方案的加解密（浮点数）

### 安装

#### 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\)

#### 密钥

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

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: {} + {}
Decrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))
#密文（张量）- 密文（张量）
result = encrypted_tensor1 - encrypted_tensor2
print("Plain equivalent: {} - {}
Decrypted result: {}.".format(plain1.tolist(), plain2.tolist(), decrypt(result)))
#密文（张量）* 密文（张量）
result = encrypted_tensor1 * encrypted_tensor2
print("Plain equivalent: {} * {}
Decrypted 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: {} * {}
Decrypted result: {}.".format(plain1.tolist(), plain.tolist(), decrypt(result)))
#取反：密文（张量）
result = -encrypted_tensor1
print("Plain equivalent: -{}
Decrypted result: {}.".format(plain1.tolist(), decrypt(result)))
#求幂：密文（张量）^3
result = encrypted_tensor1 ** 3
print("Plain equivalent: {} ^ 3
Decrypted 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 序列化

Galois密钥只会增加公共Context的大小（没有私钥）。仅当需要执行密文旋转时发送它们。

【明文数据大小：8.8 KB】