目录
- MP-SPDZ 的介绍
- 主要功能
- 典型应用场景
- MP-SPDZ 的安装
- 实验环境准备
- 环境安装
- MP-SPDZ 下载和编译
- MP-SPDZ 的使用
- 测试程序
- 第三方求和
- 三方计算
- 测试
- 冒泡排序
- 比较运算函数
- 语法详解——Sint
- 语法详解——Array
- 基于AES电路实现OPRF
- ORAM
- 隐私集合求交实现
- 两台虚拟机之间进行MPC简单实例
- 基础OT的用法
MP-SPDZ 的介绍
MP-SPDZ 是一个开源框架,用于实现各种安全多方计算协议。它由多个安全协议和优化组成,旨在提供高效的、安全的多方计算功能,适用于学术研究和实际应用。
主要功能
-
多种协议支持
- SPDZ 和 SPDZ2k:基于同态加密和秘密共享的协议。
- MASCOT:高效的秘密共享协议,适用于大规模计算。
- Overdrive:增强的 SPDZ 协议,提供更高效的离线预处理。
- Yao’s Garbled Circuits:适用于两方计算。
- GMW:基于加密电路的协议。
- BMR:用于安全电路计算的协议。
- Shamir’s Secret Sharing 和 Replicated Secret Sharing:适用于诚实多数的协议。
- Threshold ECDSA:用于门限签名的协议。
-
高效的编译和执行
- 支持将高层次的程序编译为高效的字节码,并在虚拟机上执行。
- 提供多线程支持和协议特定的优化选项。
- 支持本地和远程执行模式。
-
机器学习支持
- 支持逻辑回归和线性回归的梯度下降(SGD)训练。
- 集成 PyTorch 和 Keras 模型,支持常见的深度学习功能。
- 支持决策树的训练和预测。
-
数学和线性代数操作
- 支持整数、浮点数和定点数的基本运算和复杂操作。
- 支持矩阵运算,包括矩阵乘法、转置等。
-
输入输出操作
- 支持从文件、套接字等多种渠道输入和输出数据。
- 支持秘密输入和输出,确保数据的隐私性。
典型应用场景
-
隐私保护数据分析:
- 在不泄露各方数据隐私的前提下,进行联合数据分析和统计。
-
安全机器学习:
- 在多方协作下训练机器学习模型,确保训练数据的隐私。
-
金融数据共享:
- 银行和金融机构之间进行安全的数据交换和计算,防止数据泄露。
MP-SPDZ 的安装
实验环境准备
Ubuntu 22.04.3 LTS
环境安装
sudo apt-get install automake build-essential git libboost-dev libboost-thread-dev libntl-dev libsodium-dev libssl-dev libtool m4 python3 texinfo yasm libboost-all-dev cmake clang libgmp-dev
MP-SPDZ 下载和编译
git clone https://github.com/data61/MP-SPDZ.git
cd MP-SPDZ
make setup
Scripts/tldr.sh
make -j 8 tldr # 对spdz库进行编译
MP-SPDZ 的使用
测试程序
官方提供了一个测试程序,这个代码内容就是各种运算的小测试。
Scripts/tldr.sh
echo 1 2 3 4 > Player-Data/Input-P0-0
echo 1 2 3 4 > Player-Data/Input-P1-0
Scripts/compile-run.py -E mascot tutorial
第三方求和
在Programs/Source文件夹内新建一个源代码文件testgp.mpc(后缀名是.mpc)
vim Programs/Source/testgp.mpc
编辑内容如下:
a = sint.get_input_from(0)
b = sint.get_input_from(1)
c = sint.get_input_from(2)
sum = a + b + c
print_ln('Results =%s',sum.reveal())
运行程序
./compile.py -B 32 Programs/Source/testgp.mpc
三方计算
生成证书文件,3代表三方计算。
Scripts/setup-ssl.sh 3
写入三方数据
echo 11 > Player-Data/Input-P0-0
echo 12 > Player-Data/Input-P1-0
echo 13 > Player-Data/Input-P2-0
开三个新终端模拟三方计算,进行运算,得到结果:
./shamir-bmr-party.x -N 3 0 testgp
./shamir-bmr-party.x -N 3 1 testgp
./shamir-bmr-party.x -N 3 2 testgp
测试
介绍一个命令
./emulate.x <program>
可以用来执行编译好的程序。
对于一个测试样例testgp.mpc
- 首先编译程序:
./compile.py testgp
- 编译成功后直接执行:
./emulate.x testgp
就可以跑程序。
冒泡排序
- 编写冒泡排序算法
vim Programs/Source/merge_and_sort.mpc
from util import if_else
from Compiler import types
def maopao(a):
n = len(a)
@for_range(n)
def _(i):
@for_range(n)
def _(j):
@if_((a[i] > a[j]).reveal())
def _():
# print_ln("!!!")
tmp = a[i]
a[i] = a[j]
a[j] = tmp
return a
n = 5
a = Array(n, sfix)
@for_range_opt(n)
def _(i):
a[i] = sfix.get_input_from(0)
d = maopao(a)
print_ln('Data receive success !!')
- 程序解释
首先定义了一个函数,都是python语法,实现冒泡排序
然后指定数组大小n=5。定义一个sfix类型的数组a。sfix指安全的定点数(Secret fixed-point number represented as secret integer.)。
通过for循环将参与方的数据写入数组a。其中,get_input_from(0),0代表参与方的编号(ID)(这里是0号)。
注意for循环的写法,跟python语法还是有区别的。
3. 程序编译与执行
程序编译:merge_and_sort是程序文件名,源代码放在Programs/Source/中
./compile.py -B 32 merge_and_sort
4. 生成证书与密钥文件,建立安全的通道:在这个程序里只有一方参与计算。
Scripts/setup-ssl.sh 1
- 输入参与方的数据:数据存储在d0.dat文件里,这里是五个数,代表一个数组。
echo 8 9 7 4 6 > Player-Data/Input-P0-0
- 执行程序
./emulate.x merge_and_sort
比较运算函数
小于:less_than = lt()
大于:greater_than = gt()
小于等于: less_equal = le()
大于等于: greater_equal = ge()
等于: equal = eq()
不等于: not_equal = ne()
- 编写程序
vim Programs/Source/compare.mpc
a = sfix(12)
b = sfix(13)
print_ln("a: %s",a.reveal())
print_ln("b: %s",b.reveal())
print_ln("less_than: %s", sfix.__lt__(a,b).reveal())
print_ln("greater_than: %s", sfix.__gt__(a,b).reveal())
print_ln("less_equal: %s", sfix.__le__(a,b).reveal())
print_ln("greater_equal: %s", sfix.__ge__(a,b).reveal())
print_ln("equal: %s", sfix.__eq__(a,b).reveal())
print_ln("not_equal: %s", sfix.__ne__(a,b).reveal())
- 运行程序
./compile.py -R 128 compare
./emulate.x compare
语法详解——Sint
原博客链接
语法详解——Array
原博客链接
基于AES电路实现OPRF
- 生成数据脚本,该脚本会生成127比特(可以改,生成任意比特的)的随机数(十六进制表示),也就是key和plaintext。并将这些数据写入对应的数据文件。
vim gen_data.py
import random
import os,sys
base = [str(x) for x in range(10)] + [ chr(x) for x in range(ord('A'),ord('A')+6)]
# hex2dec
def hex2dec(string_num):
return str(int(string_num.upper(), 16))
# dec2hex
def dec2hex(string_num):
num = int(string_num)
mid = []
while True:
if num == 0: break
num,rem = divmod(num, 16)
mid.append(base[rem])
return '0x'+''.join([str(x) for x in mid[::-1]])
def gen_random_hex(n_bit):
b = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
s = "0x"
n = n_bit/4
for i in range(n):
t = random.randint(0,15)
s = s + b[t]
return s
bit = 127
a = gen_random_hex(bit)
ha = hex2dec(a)
b = gen_random_hex(bit)
hb = hex2dec(b)
print("############ gen_data.py #############")
print("a = " + a)
print("Dec(a) = " + ha)
print("b = " + b)
print("Dec(b) = " + hb)
f1 = open("Player-Data/Input-P0-0", "w")
f2 = open("Player-Data/Input-P1-0", "w")
print("Write a to Player-Data/Input-P0-0...")
f1.write(str(a))
print("Write b to Player-Data/Input-P1-0...")
f2.write(str(b))
f1.close()
f2.close()
print("################ End #################")
- 编写程序
vim Programs/Source/aes_circuit.mpc
from circuit import Circuit
sb128 = sbits.get_type(128)
key = sb128(0x2b7e151628aed2a6abf7158809cf4f3c)
plaintext = sb128(0x6bc1bee22e409f96e93d7e117393172a)
n = 1000
aes128 = Circuit('aes_128')
ciphertexts = aes128(sbitvec([key] * n), sbitvec([plaintext] * n))
ciphertexts.elements()[n - 1].reveal().print_reg()
- 编写脚本
#! /bin/bash
./compile.py -B 128 aes_circuit
Scripts/setup-ssl.sh 2
python3 gen_data.py
echo "Player-Data/Input-P0-0:" $(cat Player-Data/Input-P0-0)
echo "Player-Data/Input-P1-0:" $(cat Player-Data/Input-P1-0)
echo "########### Execute aes_circuit... #############"
Scripts/yao.sh aes_circuit
- 运行脚本
运行报错,尚未解决
ORAM
oram_tutorial.mpc
from oram import OptimalORAM
array = OptimalORAM(10000)
array[1] = 123
print_ln('%s', array[1].reveal())
编译:
./compile.py -D -R 64 oram_tutorial
运行:
./emulate.x oram_tutorial
隐私集合求交实现
vim Programs/Source/psi.mpc
from util import if_else
from Compiler import types
from Compiler import mpc_math
program.bit_length = 128
def compute_intersection(a, b):
n = len(a)
intersection = Array(n, sfix)
is_match_at = Array(n, sfix)
@for_range(n)
def _(i):
@for_range(n)
def _(j):
match = a[i] == b[j]
is_match_at[i] += match
intersection[i] = if_else(match, a[i], intersection[i])
return intersection, is_match_at
def set_intersection_example(a,b):
print_ln('Running PSI example')
intersection, is_match_at = compute_intersection(a,b)
print_ln('Printing set intersection (0: not in intersection)')
size = MemValue(sfix(0))
total = MemValue(sfix(0))
@for_range(n)
def _(i):
size.write(size + is_match_at[i])
total.write(total + intersection[i])
print_str('%s ', intersection[i].reveal())
print_ln('\nIntersection size: %s', size.reveal())
n = 10
a = Array(n, sfix)
b = Array(n, sfix)
@for_range_opt(n)
def _(i):
a[i] = sfix.get_input_from(0)
@for_range_opt(n)
def _(j):
b[j] = sfix.get_input_from(1)
print_ln('data is ok')
set_intersection_example(a,b)
编译、生成证书、写数据
./compile.py -B 64 psi
Scripts/setup-ssl.sh 2
echo 44 76 14 45 31 4 67 39 78 84 > Player-Data/Input-P0-0
echo 1864 14 44 7335 2791 564 39 9085 4 7220 > Player-Data/Input-P1-0
执行程序,要在两个终端运行:
./semi-bmr-party.x -N 2 0 psi
./semi-bmr-party.x -N 2 1 psi
两台虚拟机之间进行MPC简单实例
需要两台服务器,未完待续。。。。
基础OT的用法
useOT.cpp
#include <iostream>
#include "OT/BaseOT.h"
#include "Networking/Player.h"
#include "Tools/octetStream.h"
using namespace std;
int main()
{
int player = 0;
int nplayers = 2;
const char* servername = "127.0.0.1";
int pnb = 8000;
int my_port = 8001;
Names n = Names(player,nplayers,servername,pnb,my_port);
//Names n = Names(player,nplayers,servername,pnb,Names::DEFAULT_PORT);
RealTwoPartyPlayer tp = RealTwoPartyPlayer(n,1,101);
//PlainPlayer p = PlainPlayer(n,10);
OT_ROLE ot_role = SENDER;
//int mynum = 8000;
RealTwoPartyPlayer *s;
s = &tp;
BaseOT send = BaseOT(8,128,s, ot_role);
send.exec_base();
cout<< "Success !!" <<endl;
return 0;
}
BaseOT.h 写了两个测试程序。第一个是OT里的发送者,第二个是接受者。
程序详解:
实例化类Names用来开启并监听端口,进行通信。
实例化类RealTwoPartyPlaye 用来模拟两个参与方。
第一个参数是Names类
第二个参数:other_player,表示另一个参与者的编号
第三个参数:唯一标志id
实例化一个BaseOT类
第一个参数nOT,表示执行OT的数量,这里我设置为8,注意必须为4的倍数,要不会报错。
第二参数表示消息的长度,这里我设置为128比特。
第三个参数,参与方的类指针
第四个参数,ot的角色,这里有三个角色:发送者(SENDER),接收者(RECEIVER),两者都是(BOTH)。
实例化时候,也可以只给第三四个参数,这时候nOT和ot_length都默认为128
调用类方法exec_base()。运行的时候只需要这些参数,所有消息和选择比特都随机生成了。
useOTRec.cpp
#include <iostream>
#include "OT/BaseOT.h"
#include "Networking/Player.h"
#include "Tools/octetStream.h"
using namespace std;
int main()
{
int player = 1;
int nplayers = 2;
const char* servername = "127.0.0.1";
int pnb = 8000;
int my_port = 8002;
Names n = Names(player,nplayers,servername,pnb,my_port);
//Names n = Names(player,nplayers,servername,pnb,Names::DEFAULT_PORT);
RealTwoPartyPlayer tp = RealTwoPartyPlayer(n,0,101);
//PlainPlayer p = PlainPlayer(n,10);
OT_ROLE ot_role = RECEIVER;
//int mynum = 8000;
RealTwoPartyPlayer *s;
s = &tp;
BaseOT rec = BaseOT(8,128,s, ot_role);
rec.exec_base();
cout<< "Success !!" <<endl;
return 0;
}
makefile文件
# objs_SimpleOT = ../SimpleOT/ot_sender.o ../SimpleOT/ot_receiver.o ../SimpleOT/sc25519_random.o ../SimpleOT/ge25519_pack.o ../SimpleOT/ge25519_double.o ../SimpleOT/fe25519_pack.o
objs_SimpleOT = ../SimpleOT/*.o
all: useOT useOTRec
useOT: useOT.o
g++ -o useOT useOT.o ../OT/*.o $(objs_SimpleOT) ../libSPDZ.so -lntl -lmpirxx -lmpir -lsodium -lboost_system -lssl -lcrypto -lrt
useOTRec: useOTRec.o
g++ -o useOTRec useOTRec.o ../OT/*.o $(objs_SimpleOT) ../libSPDZ.so -lntl -lmpirxx -lmpir -lsodium -lboost_system -lssl -lcrypto -lrt
useOT.o: useOT.cpp ../Networking/Player.h ../OT/BaseOT.h
g++ -c useOT.cpp -I ~/spdz029 -march=native -g -Wextra -Wall -O3 -I. -pthread -DUSE_GF2N_LONG '-DPREP_DIR="Player-Data/"' -std=c++11 -Werror -fPIC -MMD -MP -c
useOTRec.o: useOTRec.cpp ../Networking/Player.h ../OT/BaseOT.h
g++ -c useOTRec.cpp -I ~/spdz029 -march=native -g -Wextra -Wall -O3 -I. -pthread -DUSE_GF2N_LONG '-DPREP_DIR="Player-Data/"' -std=c++11 -Werror -fPIC -MMD -MP -c
clean:
rm -f *.o main *.out useOT useOTRec *.d