#教程2:CrypTensors内部
注意:本教程是可选的,可以跳过,而不会丢失以下教程的连续性。
在本教程中,我们将简要介绍“CrypTensors”的内部结构。
使用“mpc”后端,“CrypTensor”是使用安全mpc协议加密的张量,称为“MPCSensor”。为了支持“MPCSensor”所需的数学运算,CrypTen实现了两种秘密共享协议:算术秘密共享和二进制秘密共享。算术秘密共享是“MPCSencer”实现的大多数数学运算的基础。类似地,二进制秘密共享允许对逻辑表达式进行求值。
在本教程中,我们将首先介绍`CrypTensor`<i>ptype</i>(即<i>私有类型</i>)的概念,并演示如何使用它来获得使用算术和二进制秘密共享的`MPCSensor`。我们还将描述如何使用这些<i>ptypes</i>中的每一个,以及如何将他们结合。
#import the libraries
import crypten
import torch
#initialize crypten
crypten.init()
#Disables OpenMP threads -- needed by @mpc.run_multiprocess which uses fork
torch.set_num_threads(1)
CrypTen定义了“MPCSensor”的“ptype”(对于<i>私有类型</i>)属性,以表示“CrypTensor”中使用的秘密共享协议的类型。“ptype”在许多方面类似于PyTorch的“dtype”。“ptype”可以有两个值:
-`ArithmeticSharedSensors的`crypten.mpc.athmeth``</li>
-`Crypton.mpc.binary`用于`BinarySharedSensor`</li>
我们可以使用“ptype”属性创建具有适当秘密共享协议的“CrypTensor”。例如
#Constructing CrypTensors with ptype attribute
#arithmetic secret-shared tensors
x_enc = crypten.cryptensor([1.0, 2.0, 3.0], ptype=crypten.mpc.arithmetic)
print("x_enc internal type:", x_enc.ptype)
#binary secret-shared tensors
y = torch.tensor([1, 2, 1], dtype=torch.int32)
y_enc = crypten.cryptensor(y, ptype=crypten.mpc.binary)
print("y_enc internal type:", y_enc.ptype)
x_enc internal type: ptype.arithmetic
y_enc internal type: ptype.binary
算术秘密共享
让我们更仔细地看一下“crypten.mpc.athmetic”<i>ptype</i>。“CrypTensors”实现的大多数数学运算都是使用算术秘密共享实现的。因此,“crypten.mpc.athmetic”是新生成的“Cryp张量”的默认<i>ptype</i>。
让我们首先使用“ptype=crypten.mpc.athmetic”创建一个新的“CrypTensor”,以强制加密是通过算术秘密共享完成的。我们可以打印每个共享的值,以确认值被正确加密。
要做到这一点,我们需要创建多个政党来持有每一份股份。我们在这里使用`@mpc.run_multiprocess`函数decorator来完成这项工作,我们开发该函数是为了从单个脚本中执行crypten代码(正如我们在Jupyter笔记本中所做的那样)。CrypTen遵循标准的MPI编程模型:它为每一方运行一个单独的进程,但每个进程运行一个相同(完整)的程序。每个进程都有一个“秩”变量来标识自己。
请注意,下面两个“_tensor”属性的总和等于输入的缩放表示。(因为MPC要求值为整数,所以我们在加密之前将输入浮点值缩放为定点编码。)
import crypten.mpc as mpc
import crypten.communicator as comm
@mpc.run_multiprocess(world_size=2)
def examine_arithmetic_shares():
x_enc = crypten.cryptensor([1, 2, 3], ptype=crypten.mpc.arithmetic)
rank = comm.get().get_rank()
crypten.print(f"\nRank {rank}:\n {x_enc}\n", in_order=True)
x = examine_arithmetic_shares()
Rank 0:
MPCTensor(
_tensor=tensor([ 4977840465844292698, 234311463858409737, -8644375101282040029])
plain_text=HIDDEN
ptype=ptype.arithmetic
)
Rank 1:
MPCTensor(
_tensor=tensor([-4977840465844227162, -234311463858278665, 8644375101282236637])
plain_text=HIDDEN
ptype=ptype.arithmetic
)
###二进制秘密共享
CrypTen中实现的第二种秘密共享是二进制或XOR秘密共享。这种类型的秘密共享可以提高评估逻辑表达式的效率。
让我们更仔细地看一下`crypten.mpc.binary`<i>ptype</i>。“CrypTensors”实现的大多数逻辑运算都是使用算术秘密共享实现的。当我们想计算二进制运算符(如“^&|><<`等”)或逻辑运算(如比较器)时,我们通常会使用这种类型的秘密共享。
让我们首先使用“ptype=crypten.mpc.binary”创建一个新的“CrypTensor”,以强制加密是通过二进制秘密共享完成的。我们可以打印每个共享的值,以确认值被正确加密,就像我们对算术秘密共享所做的那样。
(请注意,下面两个“_tensor”属性的xor等于输入的未缩放版本。)
@mpc.run_multiprocess(world_size=2)
def examine_binary_shares():
x_enc = crypten.cryptensor([2, 3], ptype=crypten.mpc.binary)
rank = comm.get().get_rank()
crypten.print(f"\nRank {rank}:\n {x_enc}\n", in_order=True)
x = examine_binary_shares()
Rank 0:
MPCTensor(
_tensor=tensor([-3617348383499570248, -581960226550774565])
plain_text=HIDDEN
ptype=ptype.binary
)
Rank 1:
MPCTensor(
_tensor=tensor([-3617348383499570246, -581960226550774568])
plain_text=HIDDEN
ptype=ptype.binary
)
###使用两种秘密共享协议
通常,数学函数可能需要同时使用加法和异或秘密共享来进行有效评估。需要在共享类型之间进行转换的函数包括比较器(`>,>=,<,<=,==,!=`)以及从它们派生的函数(`abs,sign,relu`等)。有关支持功能的完整列表,请参阅CrypTen文档。
CrypTen提供了允许在<i>ptypes</i>之间转换的功能。<i>ptypes</i>之间的转换可以使用带有“crypten.ptype”输入的“.to()”函数来完成,也可以通过调用“.athmetic()”和“.binary()”转换函数来完成。
from crypten.mpc import MPCTensor
@mpc.run_multiprocess(world_size=2)
def examine_conversion():
x = torch.tensor([1, 2, 3])
rank = comm.get().get_rank()
# create an MPCTensor with arithmetic secret sharing
x_enc_arithmetic = MPCTensor(x, ptype=crypten.mpc.arithmetic)
# To binary
x_enc_binary = x_enc_arithmetic.to(crypten.mpc.binary)
x_from_binary = x_enc_binary.get_plain_text()
# print only once
crypten.print("to(crypten.binary):")
crypten.print(f" ptype: {x_enc_binary.ptype}\n plaintext: {x_from_binary}\n")
# To arithmetic
x_enc_arithmetic = x_enc_arithmetic.to(crypten.mpc.arithmetic)
x_from_arithmetic = x_enc_arithmetic.get_plain_text()
# print only once
crypten.print("to(crypten.arithmetic):")
crypten.print(f" ptype: {x_enc_arithmetic.ptype}\n plaintext: {x_from_arithmetic}\n")
WARNING:root:module 'torchvision.models.mobilenet' has no attribute 'ConvBNReLU'
x_enc internal type: ptype.arithmetic
y_enc internal type: ptype.binary
Rank 0:
MPCTensor(
_tensor=tensor([ 4977840465844292698, 234311463858409737, -8644375101282040029])
plain_text=HIDDEN
ptype=ptype.arithmetic
)
Rank 1:
MPCTensor(
_tensor=tensor([-4977840465844227162, -234311463858278665, 8644375101282236637])
plain_text=HIDDEN
ptype=ptype.arithmetic
)
Rank 0:
MPCTensor(
_tensor=tensor([-3617348383499570248, -581960226550774565])
plain_text=HIDDEN
ptype=ptype.binary
)
Rank 1:
MPCTensor(
_tensor=tensor([-3617348383499570246, -581960226550774568])
plain_text=HIDDEN
ptype=ptype.binary
)
to(crypten.binary):
ptype: ptype.binary
plaintext: tensor([1., 2., 3.])
to(crypten.arithmetic):
ptype: ptype.arithmetic
plaintext: tensor([1., 2., 3.])
z = examine_conversion()
##数据源
CrypTen遵循标准的MPI编程模型:它为每一方运行一个单独的进程,但每个进程运行一个相同(完整)的程序。每个进程都有一个“秩”变量来标识自己。
如果秩为“i”的进程是数据“x”的源,则“x”以“i”作为其源值(表示为“src”)进行加密。然而,MPI协议要求两个进程都提供与其输入大小相同的张量。加密时,CrypTen会忽略非源进程提供的所有数据。
在下一个示例中,我们将展示如何使用“rank”和“src”值来加密张量。这里,我们将使三方中的每一方生成一个值“x”,该值等于其自身的“秩”值。在循环中,创建了3个加密张量,每个张量都有不同的源。当这些张量被解密时,我们可以验证张量是使用源进程提供的张量生成的。
(请注意,如果没有提供,“crypten.cryptensor”将秩0用作默认源。)
@mpc.run_multiprocess(world_size=3)
def examine_sources():
# Create a different tensor on each rank
rank = comm.get().get_rank()
x = torch.tensor(rank)
crypten.print(f"Rank {rank}: {x}", in_order=True)
#
world_size = comm.get().get_world_size()
for i in range(world_size):
x_enc = crypten.cryptensor(x, src=i)
z = x_enc.get_plain_text()
# Only print from one process to avoid duplicates
crypten.print(f"Source {i}: {z}")
x = examine_sources()
Rank 0: 0
Rank 1: 1
Rank 2: 2
Source 0: 0.0
Source 1: 1.0
Source 2: 2.0