
MP-SPDZ的学习与运用
MP-SPDZ 是一个开源框架,用于实现各种安全多方计算协议。它由多个安全协议和优化组成,旨在提供高效的、安全的多方计算功能,适用于学术研究和实际应用。
MP-SPDZ: A Versatile Framework for Multi-Party Computation
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 模型,支持常见的深度学习功能。
- 支持决策树的训练和预测。
-
数学和线性代数操作
- 支持整数、浮点数和定点数的基本运算和复杂操作。
- 支持矩阵运算,包括矩阵乘法、转置等。
-
输入输出操作
- 支持从文件、套接字等多种渠道输入和输出数据。
- 支持秘密输入和输出,确保数据的隐私性。
典型应用场景
-
隐私保护数据分析:
- 在不泄露各方数据隐私的前提下,进行联合数据分析和统计。
-
安全机器学习:
- 在多方协作下训练机器学习模型,确保训练数据的隐私。
-
金融数据共享:
- 银行和金融机构之间进行安全的数据交换和计算,防止数据泄露。
论文解读
摘要
Multi-Protocol SPDZ (MP-SPDZ)是SPDZ-2的一个分支,扩展了SPDZ-2到34种MPC协议变体,所有这些变体都可以使用基于Python的相同高级编程接口。这极大地简化了比较不同协议和安全模型成本的过程。这些协议涵盖了所有常用的安全模型(诚实/不诚实的多数和半诚实/恶意的腐败),以及二进制和算术电路的计算。底层使用的原语包括秘密共享、无意识传输、同态加密和混淆电路。实现的协议广泛,结合易于访问的高级接口,使其适合于研究人员在安全计算领域进行计算成本的基准测试。本文旨在概述MP-SPDZ的各种协议实现和在其开发过程中所做的设计选择,以及编程接口的功能。
主要贡献
- MP-SPDZ提供了一个实现了30种协议变体的框架,使得用户可以在同一个虚拟机上进行多种设置下的基准测试,从而简化了不同安全模型之间成本的比较,以及在相同安全模型下协议的比较。
- MP-SPDZ的设计理念在于将常用的安全计算协议归纳为几种相似的操作,如输入、输出、本地可计算的线性操作、AND或乘法等,从而构建一个通用框架,以便尽可能地重用组件和优化。
- MP-SPDZ还提供了独立的Linux二进制文件和高级语言的广泛文档,降低了进行安全计算的门槛,使得研究人员可以更轻松地进行安全计算的实验和研究
协议实现
不诚实多数
在Dishonest Majority协议中,MP-SPDZ实现了多种协议,这些协议使用与公钥加密相关的技术,如无差错传输或同态加密。Nielsen等人和Damgård等人开展了与此相关的实用协议研究。Nielsen等人提出了一种基于无差错传输的两方协议,用于计算基于无差错传输的二进制电路(后来被称为TinyOT),而Damgård等人提出了一种多方协议,用于在域中进行计算(模素数或F2k),使用同态加密(被作者命名为SPDZ)。这些协议使用了Beaver的技术,将计算分为数据独立和数据相关两个阶段。MASCOT是一种计算Beaver三元组的协议,使用了具有恶意安全性的无差错传输,这是SPDZ-2中发布的第一个离线阶段。SPDZ2k将MASCOT适应到了模2的幂的计算中。SPDZ和Overdrive是两个协议,使用半同态和略同态加密计算Beaver三元组。这些协议旨在最小化同态加密的级别,以提高性能。MP-SPDZ实现了SPDZ2k,并使用自己优化的Z2k实现了SPDZ2k。
诚实多数
诚实多数是指安全多方计算中的一种设置,其中可以使用秘密共享安全地执行计算,而无需遗忘传输或同态加密。MP-SPDZ 利用两种秘密共享方案,即复制的秘密共享和 Shamir 的秘密共享,在诚实多数设置中实现安全计算 。复制的秘密共享要求每一方存储三个环元素,而 Shamir 的秘密共享允许在不通信的情况下对两个共享的乘法进行本地计算,尽管会导致不同的秘密共享方案。涉及通信的重新共享协议可以转换回原始共享方案,从而促进进一步的繁殖。重新共享协议是线性的,使各方能够以单次乘法的通信成本计算内积 。
恶意计算
恶意计算是指在安全多方计算中,参与方可能不遵守协议,试图获得其他参与方的私密信息或干扰计算过程。为了应对恶意行为,需要采用特定的安全协议和技术来确保计算的安全性和隐私性。恶意计算的研究旨在设计能够抵御各种攻击和欺诈行为的计算框架,以保护参与方的数据和隐私信息。
高级协议
在高级协议中,多方计算的实现通常涉及算术黑盒,即私密输入、加法、乘法和公开输出。然而,许多计算,比如比较,需要进一步的相关随机性,尤其是在更大的域中需要秘密随机比特。Damgård等人指出,可以通过使用一个乘法和一个开放操作来产生一个秘密随机比特,从而在计算模素数的情况下实现一个秘密随机比特,而仅需付出较小的代价。他们后来将这一方法扩展到了计算模二的情况。
总体架构
虚拟机
高级设计
虚拟机的高级设计包括实现了SPDZ在线阶段的计算,针对大素数p和F2n进行计算,其中n = 40或n = 128。MP-SPDZ添加了一系列协议,用Z2k替换了Fp,同时增加了布尔电路的计算。这种设计根植于Keller和Yanai的实现,并默认使用长度为64的向量。
基本数据类型
虚拟机允许为每个计算域和64位整数处理公共和秘密值的数据。在每个域中在另一个整数类型之上有明确的数据类型的原因是计算域中数字的大小不同,并且Fp中的数字使用Montgomery表示存储
寄存器
虚拟机为每种基本数据类型提供无限数量的寄存器。虽然这没有基于堆栈的设计复杂,但它允许更简单的实现。寄存器号被硬编码到字节码中,这使得虚拟机能够为计算分配足够的数字。寄存器通常用于存储指令的输入和输出,并且它们是线程本地的。
内存
对于更复杂的数据结构,如数组、矩阵和高维结构,虚拟机为每一种基本数据类型提供另一种工具,称为内存。内存数组是全局的,因此允许线程之间通信信息。与寄存器不同,可以使用存储在整数寄存器中的运行时值访问内存。内存必须在编译时分配。
指令集
- 复制:这包括初始化寄存器、寄存器和内存之间的复制,以及不同公共数据类型寄存器之间的转换。
- 简单计算:用于所有不需要通信的计算的指令,包括对秘密值进行的线性操作。
- 安全协议:这一类指令允许无限数量的参数,以便减少通信轮次。这些协议包括私有输入、乘法和公共输出等基本协议,以及更专门的协议,如内积、矩阵乘法、2D卷积,以及Dalskov等人提出的特殊截断操作。
- 预处理信息:预处理是批量执行的,这意味着这类指令只在必要时进行通信。还可以通过磁带通信所需的预处理信息量,以优化预处理过程。
矢量化
大多数指令都是矢量化的,即它们意味着对请求的连续寄存器执行相同的操作。这在编译期间和执行期间显著减少了表示重复计算的开销。虚拟机还支持结构化地将值加载到连续寄存器中,例如,在多维数组中的任何维度加载行。
多线程
虚拟机实现了多线程,其中计算在主线程中运行,由称为“tape”的指令列表描述。其可以在其他线程中启动更多的“tape”并等待它们完成。然而,最大线程数必须在编译时知道。尽管存在这种限制,但虚拟机提供的功能足够强大,适用于许多需要多线程的应用,比如矩阵乘法或卷积。
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语法还是有区别的。
-
程序编译与执行:程序编译: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
基于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
两端通信
程序一共有两端,一个是客户端client.cpp,一个是服务器端server.cpp
客户端代码:client.cpp
#include <iostream>
#include "mytools.hpp"
#include "Networking/Player.h"
#include "Tools/octetStream.h"
#include "Tools/FlexBuffer.h"
#include "Networking/sockets.h"
#include "Tools/Hash.h"
#include "Tools/int.h"
#include "Networking/Receiver.h"
#include "Networking/Sender.h"
#include "Tools/ezOptionParser.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 = 8005;
const Names n = Names(player,nplayers,servername,pnb,my_port);
PlainPlayer p = PlainPlayer(n,10);
//MultiPlayer<int> p = MultiPlayer<int>(n);
string str = "";
auto m2 = octetStream(str);
p.receive_player_no_stats(0,m2);
print(m2.get_data());
str = "Hello222 !!";
auto m1 = octetStream(str);
p.send_to_no_stats(0,m1);
cout<< "Success !!" <<endl;
return 0;
}
服务器端代码:server.cpp
#include <iostream>
#include "mytools.hpp"
#include "Networking/Player.h"
#include "Tools/octetStream.h"
#include "Tools/FlexBuffer.h"
#include "Networking/sockets.h"
#include "Tools/Hash.h"
#include "Tools/int.h"
#include "Networking/Receiver.h"
#include "Networking/Sender.h"
#include "Tools/ezOptionParser.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);
PlainPlayer p = PlainPlayer(n,10);
string str = "Hello111 !!";
auto m1 = octetStream(str);
p.send_to_no_stats(1,m1);
str = "";
auto m2 = octetStream(str);
p.receive_player_no_stats(1,m2);
print(m2.get_data());
cout<< "Success !!" <<endl;
return 0;
}
mytools.hpp
#include <iostream>
using namespace std;
template<class T>
void print(T s)
{
cout << s << endl;
}
- 开启一个终端,运行以下命令
vim server.cpp
g++ -g -c server.cpp -I ~/mp-spdz-0.3.8
g++ -g -o server server.o ~/mp-spdz-0.3.8/libSPDZ.so
./server
- 开启另外一个终端,运行以下命令
vim client.cpp
g++ -g -c client.cpp -I ~/mp-spdz-0.3.8
g++ -g -o client client.o ~/mp-spdz-0.3.8/libSPDZ.so
链接
更多推荐
所有评论(0)