SPU隐私求交#

以下代码仅供演示。出于系统安全考虑,请 不要 直接用于生产。

PSI(Private Set Intersection) is a cryptographic technique that allows two parties holding sets to compare encrypted versions of these sets in order to compute the intersection. In this scenario, neither party reveals anything to the counterparty except for the elements in the intersection.

在隐语中,SPU设备支持三种隐私求交算法:

  • ECDH:半诚实模型, 基于公钥密码学,适用于小数据集。

  • KKRT:半诚实模型, 基于布谷鸟哈希(Cuckoo Hashing)以及高效不经意传输扩展(OT Extension),适用于大数据集。

  • BC22PCG:半诚实模型, 基于随机相关函数生成器。

在开始之前,我们需要初始化环境。以下三个节点 alicebobcarol 是在一台机器上创建的,以模拟多个参与者。

[1]:
import secretflow as sf

# In case you have a running secretflow runtime already.
sf.shutdown()

sf.init(['alice', 'bob', 'carol'], address='local')

准备数据集#

首先,我们需要一个数据集来构建垂直分区的场景。为了简单起见,我们在这里使用 iris 数据集。我们在其中添加两列,用于后续的单键和多键求交演示。

  • uid:唯一ID。

  • month:模拟每月生成样本的场景,前50%的样本在1月份生成,后50%的样本在2月份生成。

[2]:
import numpy as np
from sklearn.datasets import load_iris

data, target = load_iris(return_X_y=True, as_frame=True)
data['uid'] = np.arange(len(data)).astype('str')
data['month'] = ['Jan'] * 75 + ['Feb'] * 75

data
[2]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) uid month
0 5.1 3.5 1.4 0.2 0 Jan
1 4.9 3.0 1.4 0.2 1 Jan
2 4.7 3.2 1.3 0.2 2 Jan
3 4.6 3.1 1.5 0.2 3 Jan
4 5.0 3.6 1.4 0.2 4 Jan
... ... ... ... ... ... ...
145 6.7 3.0 5.2 2.3 145 Feb
146 6.3 2.5 5.0 1.9 146 Feb
147 6.5 3.0 5.2 2.0 147 Feb
148 6.2 3.4 5.4 2.3 148 Feb
149 5.9 3.0 5.1 1.8 149 Feb

150 rows × 6 columns

在实际场景中,示例数据由每个参与者提供,求交的字段需提前约定:

  • 交集字段可以为单个字段,也可以为多个字段,需存在于对应方数据集中。

  • 交集字段必须是唯一的。如果存在重复数据,需要在求交前进行去重处理。

举个例子,下面是alice提供的用于隐私求交的数据,求交字段是 uidmonth,我们可以看到[1,’Jan’]是重复的。

alice.csv
---------
uid   month   c0
1     Jan     5.8
2     Jan     5.4
1     Jan     5.8
1     Feb     7.4

去重后的数据集如下

alice.csv
---------
uid   month   c0
1     Jan     5.8
2     Jan     5.4
1     Feb     7.4

我们对 iris 数据进行了 3 次随机采样,以模拟 alicebobcarol 提供的数据,三个数据处于未对齐状态。

[3]:
import os

os.makedirs('.data', exist_ok=True)
da, db, dc = data.sample(frac=0.9), data.sample(frac=0.8), data.sample(frac=0.7)

da.to_csv('.data/alice.csv', index=False)
db.to_csv('.data/bob.csv', index=False)
dc.to_csv('.data/carol.csv', index=False)

两方隐私求交#

我们在物理设备上虚拟化三个逻辑设备:

  • alice, bob: PYU设备,负责参与者的本地明文计算。

  • spu:SPU设备,由alice和bob组成,负责双方的密文计算。

[4]:
alice, bob = sf.PYU('alice'), sf.PYU('bob')
spu = sf.SPU(sf.utils.testing.cluster_def(['alice', 'bob']))

单键隐私求交#

接下来,我们使用 uid 作为求交列来处理这两个数据,SPU提供 psi_csv 函数, psi_csv 将csv文件作为输入,并在求交后生成csv文件。默认协议为KKRT。

[5]:
input_path = {alice: '.data/alice.csv', bob: '.data/bob.csv'}
output_path = {alice: '.data/alice_psi.csv', bob: '.data/bob_psi.csv'}
spu.psi_csv('uid', input_path, output_path, 'alice')

为了校验结果的正确性,我们使用 pandas.DataFrame.join 对da和db进行内部求交。可以看到,这两个数据已经根据``uid``对齐,并根据它们的字典顺序排序。

[6]:
import pandas as pd

df = da.join(db.set_index('uid'), on='uid', how='inner', rsuffix='_bob', sort=True)
expected = df[da.columns].astype({'uid': 'int64'}).reset_index(drop=True)

da_psi = pd.read_csv('.data/alice_psi.csv')
db_psi = pd.read_csv('.data/bob_psi.csv')

pd.testing.assert_frame_equal(da_psi, expected)
pd.testing.assert_frame_equal(db_psi, expected)

da_psi
[6]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) uid month
0 6.3 3.3 6.0 2.5 100 Feb
1 5.8 2.7 5.1 1.9 101 Feb
2 7.1 3.0 5.9 2.1 102 Feb
3 6.3 2.9 5.6 1.8 103 Feb
4 6.5 3.0 5.8 2.2 104 Feb
... ... ... ... ... ... ...
101 5.6 2.7 4.2 1.3 94 Feb
102 5.7 2.9 4.2 1.3 96 Feb
103 6.2 2.9 4.3 1.3 97 Feb
104 5.1 2.5 3.0 1.1 98 Feb
105 5.7 2.8 4.1 1.3 99 Feb

106 rows × 6 columns

多键隐私求交#

我们还可以使用多个字段来求交,下面演示了如何使用 uidmonth 来求交两个数据。在实现方面,多个字段被连接成一个字符串,因此请确保没有重复的多列复合键。

[7]:
spu.psi_csv(['uid', 'month'], input_path, output_path, 'alice')

类似地,我们使用 panda.dataframe.join 来验证结果的正确性,可以看到这两个数据已经根据 uidmonth 对齐,并根据它们的字典序排序。

[8]:
df = da.join(db.set_index(['uid', 'month']), on=['uid', 'month'], how='inner', rsuffix='_bob', sort=True)
expected = df[da.columns].astype({'uid': 'int64'}).reset_index(drop=True)

da_psi = pd.read_csv('.data/alice_psi.csv')
db_psi = pd.read_csv('.data/bob_psi.csv')

pd.testing.assert_frame_equal(da_psi, expected)
pd.testing.assert_frame_equal(db_psi, expected)

三方隐私求交#

接下来,我们添加第三方 carol ,并为其创建一个PYU设备,以及一个由三方构建的SPU设备。

[9]:
carol = sf.PYU('carol')
spu_3pc = sf.SPU(sf.utils.testing.cluster_def(['alice', 'bob', 'carol']))

然后使用 uidmonth 作为求交键来执行三方求交。目前三方隐私求交暂时只支持ECDH协议。

[10]:
input_path = {alice: '.data/alice.csv', bob: '.data/bob.csv', carol: '.data/carol.csv'}
output_path = {alice: '.data/alice_psi.csv', bob: '.data/bob_psi.csv', carol: '.data/carol_psi.csv'}
spu_3pc.psi_csv(['uid', 'month'], input_path, output_path, 'alice', protocol='ECDH_PSI_3PC')

最后我们同样使用 panda.dataframe.join 来验证结果的正确性。

[11]:
keys = ['uid', 'month']
df = da.join(db.set_index(keys), on=keys, how='inner', rsuffix='_bob', sort=True).join(
    dc.set_index(keys), on=keys, how='inner', rsuffix='_carol', sort=True)
expected = df[da.columns].astype({'uid': 'int64'}).reset_index(drop=True)

da_psi = pd.read_csv('.data/alice_psi.csv')
db_psi = pd.read_csv('.data/bob_psi.csv')
dc_psi = pd.read_csv('.data/carol_psi.csv')

pd.testing.assert_frame_equal(da_psi, expected)
pd.testing.assert_frame_equal(db_psi, expected)
pd.testing.assert_frame_equal(dc_psi, expected)

接下来#

通过本教程,我们了解了如何通过SPU进行两方和三方数据求交。在得到数据交集后,我们可以对对齐的数据集进行机器学习建模。

  • 逻辑回归:使用JAX在SPU上进行逻辑回归训练。

  • 神经网络:用JAX在SPU上进行神经网络训练。

  • 拆分学习:使用TensorFlow进行神经网络拆分学习。