写点什么

隐私 AI 框架中 MPC 协议的快速集成

  • 2020-10-12
  • 本文字数:6177 字

    阅读完需:约 20 分钟

隐私AI框架中MPC协议的快速集成

我们在上一篇文章中,介绍了为了实现隐私 AI 系统的易用性,如何对 TensorFlow 这样的深度学习框架进行深度的改造。 本篇文章进一步进入 TensorFlow 后端算子的内部实现,阐述 Rosetta 中如何通过定义通用的密码协议抽象接口层实现系统解耦,使得隐私计算研究者可以轻松地将 MPC 协议这样的隐私计算技术给集成进来。


在第一篇整体介绍中,我们简要对比过 PySyft [1] 等探索性隐私 AI 框架,它们都是在 PyTorch 等深度学习框架之上,在 Python 接口层利用高层 API 来实现密码学协议的。这种方式虽然具有可以直接复用 AI 框架提供的接口、简化在 Python 进行的并发优化等优点,但也使得密码学专家等隐私计算技术的开发者必须要了解具体的 AI 框架。


此外,由于密码学的基础运算较为耗时,所以实际中为了有更高性能的实现方式,大部分相关的优秀基础库软件都是用 C/C++ 等语言实现,而且还会融合不同底层硬件体系结构下的指令集做进一步的加速。


所以,从便利隐私计算开发者、系统性能提升等角度出发,Rosetta 在后端将隐私计算技术的具体实现给抽象解耦出来,定义了一层较为通用的抽象接口。当开发者需要引入定制化的新隐私算法协议时,只需要参考接口定义规范,自由地按照自己熟悉的方式实现基础功能,就可以很快地将新功能引入进来。而在 Python API 层使用时,通过一个接口的调用就可以完成协议的切换。


接下来,我们首先会整体介绍 Rosetta 中为支持协议扩展所设计的抽象接口层,然后在第二部分结合一个 Naive 协议示例,来具体说明如何基于这些组件快速的集成一个新的自定义 MPC(Multi-Party Computation,安全多方计算)协议。


注意:

目前,MPC 是基于密码学手段实现隐私计算这个方向上使用的最主要具体技术。所以,下文中除特别指明外,我们所称的“隐私协议”、“密码协议”都是指 MPC 安全协议。

这里的相关介绍仍基于 Rosetta V0.2.1 版本,后续随着项目的迭代优化,可能会有局部调整。

密码协议统一接口模块

为了使得整体架构上足够的灵活、可扩展,Rosetta 的后端 C++ 开发中同样遵循经典的 SOLID 原则 [2] 来进行整体的设计。整个的密码协议统一抽象接口层根据功能职责进一步的划分为三个不同层次,并分别封装为 ProtocolManagerProtocolBaseProtocolOps 三个类,其中第一个类ProtocolManager 是一个单例(Singleton)类,是上层 API、TensorFlow 的后端算子实现中所需要唯一感知的组件。而ProtocolBaseProtocolOps 则是两个接口类,由它们定义统一的各个后端具体密码协议所需要实现的功能。这三个类之间的整体关系如下:



细心的读者应该还记得,我们在上一篇文章的最后指出,在 TensorFlow 后端算子组SecureOpkernel实现中会最终调用这个模块:


// call protocol opsvector<string> outstr(m*n);ProtocolManager::Instance()->GetProtocol()->GetOps(msg_id().str())->Matmul(in1, in2, outstr, &attrs_);
复制代码


这行语句结合上述 UML 类图,可以很清晰地看出各个组件之间的调用链关系:通过协议管理组件入口获取当前上下文的协议对象,协议对象通过算子入口进一步调用具体某一算子函数。


下面就让我们分别简要介绍下这三个核心类。

ProtocolManager

ProtocolManager 是总的入口,负责整体的控制。其内部为了支持多个可选协议,会维护一个协议名到ProtocolBase指针对象的映射表,并据此进行上下文的控制。除了Instance 方法是常规的取得这个 Singleton 的对象实例外,它的功能接口可以分为两大类,一类是面向上层 Python SDK 的,一类是面向开发者进行协议扩展时加以支持的。


  • 上层 Python 层的一些协议相关的 API,如activatedeactivate 等,会内部调用ProtocolManagerActivateProtocolDeactivateProtocol等方法,来实现对当前使用的协议的控制。而这些类成员函数的内部会进一步的调用ProtocolBase 接口基类的InitUninit 等方法。

  • 而当我们需要引入一个新的协议时,在这一层所需要做的仅仅是调用其RegisterProtocol方法来注册这个新的协议即可。

ProtocolBase

ProtocolBase 是每一个具体的协议都需要最终实现的接口基类。其中Init接口定义如何进行协议的初始化,具体协议中需要在这个接口中根据用户传入的配置信息,实现多方之间网络的建立、本地秘钥和随机数的设置等操作。其函数原型如下:


  /**   * @desc: to init and activate this protocol.    *         Start the underlying network and prepare resources.   * @param:   *     config_json_str: a string which contains the protocol-specific config.   * @return:   *     0 if success, otherwise some errcode   * @note:   *   The partyID for MPC protocol is also included in the config_json_str,   *   you may need extract it.   */  virtual int Init(string config_json_str = "");
/** * @desc: to uninit and deactivate this protocol. * @return: * 0 if success, otherwise some errcode */ virtual int Uninit();
复制代码


在 Rosetta 中,为了进一步便于简单协议的集成,我们用一个子类 MpcProtocol 封装了可以复用的一些功能,比如一般 MPC 协议中常用的一个技巧是:多方两两之间通过设定共同的 shared key 来配置伪随机数发生器 PRG,这样可以减少实现协议时多方之间的交互次数和通讯量。这个子类中就基于这些可能可以复用的功能实现了 ProtocolBase 中的InitUbinit 方法。


另一个主要的方法GetOps 则会进一步调用对应协议的ProtrocolOps的子类对象来进一步 delegate 具体算子的实现。


以 Rosetta 中定制化实现的 SecureNN 协议为例,我们是通过SnnProtocol 这个子类来具体实现的。其类继承关系图如下:


ProtocolOps

ProtocolOps 用于封装各个安全协议中具体所需要实现的算子接口。大部分基础算子的函数原型中,都以字符串 向量作为参数类型,并可以通过一个可选的参数传入相关属性信息,比如Add的函数原型是:


# `attr_type` is just an inner alias for `unordered_map<string, string>`int Add(const vector<string>& a,      const vector<string>& b,      vector<string>& output,      const attr_type* attr_info = nullptr);
复制代码


注意: 我们在前面的文章中介绍过,在 Rosetta 内部为了支持多种后端协议中自定义的密文格式,我们统一在外层用字符串来封装密文数据,所以这里参数的基础类型都是字符串。


各个具体的协议需要进一步的实现各个算子函数,比如,在 Rosetta 中实现的 SecureNN 协议中的各个函数的实现是在子类SnnProtocolOps 中加以实现:



在这些具体的各个函数内部实现中,基本的步骤是先将字符串解码为此协议内部所设定的类型,然后进一步的进行多方之间安全的逻辑计算(这里一般是需要进行通信交互的),最后再将得到的内部计算结果编码为字符串输出到出参中。比如下面是 SnnProtocolOps 中矩阵乘法函数 Matmul 的代码片段:


int SnnProtocolOps::Matmul(const vector<string>& a,                const vector<string>& b,                vector<string>& output,                const attr_type* attr_info) {  int m = 0, k = 0, n = 0;  if (attr_info->count("m") > 0 && attr_info->count("n") > 0 && attr_info->count("k") > 0) {    m = std::stoi(attr_info->at("m"));    k = std::stoi(attr_info->at("k"));    n = std::stoi(attr_info->at("n"));  } else {    log_error << "please fill m, k, n for SnnMatmul(x, y, m, n, k, transpose_a, transpose_b) ";    return -1;  }
bool transpose_a = false, transpose_b = false; if (attr_info->count("transpose_a") > 0 && attr_info->at("transpose_a") == "1") transpose_a = true; if (attr_info->count("transpose_b") > 0 && attr_info->at("transpose_b") == "1") transpose_b = true;
vector<mpc_t> out_vec(m * n); vector<mpc_t> private_a, private_b; snn_decode(a, private_a); snn_decode(b, private_b);
std::make_shared<rosetta::snn::MatMul>(_op_msg_id, net_io_) ->Run(private_a, private_b, out_vec, m, k, n, transpose_a, transpose_b);
snn_encode(out_vec, output); return 0;}
复制代码


从中可以看出,我们先从属性信息中直接取出矩阵输入参数的 shape 信息,然后将输入数据通过snn_decode 转换为内部的mpc_t 类型。再调用根据 SecureNN 的协议算法实现的多方协同计算的内部函数 MatMul之后就会得到更新之后的结果密文数据,最后通过snn_encode 重新将密文数据由mpc_t 转换为字符串类型加以输出。


  • SecureNN 中的mpc_t 类型就是 uint64_t (如果用户配置了使用 128 位的大整数,则是 uint128_t)。因为很多密码学的基础操作都需要在抽象代数的环(ring)、域(field)上进行(同时,最新 SecureNN 等 MPC 协议又为了充分利用基础硬件的运算加速,已经支持直接在整数环上进行运算处理),所以转换到大整数上几乎是所有相关隐私技术必要的内部操作。

示例:Naive 协议的集成

下面,我们结合一个示例协议 Naive 来具体演示下如何快速集成 MPC 协议到 Rosetta 中。


注意:

这个 Naive 协议是一个不安全的、仅用于演示的协议!不要 naive 的在任何生产环境下使用此协议!

这个 Naive 协议是一个不安全的、仅用于演示的协议!不要 naive 的在任何生产环境下使用此协议!

这个 Naive 协议是一个不安全的、仅用于演示的协议!不要 naive 的在任何生产环境下使用此协议!


我们仅在 Naive 协议中实现最基本的加法和乘法等操作。在这个“协议”中,P0P1 会将自己的私有输入值平均分为两份,一份自己持有,另一份发送给对方,作为各自的“密文”。然后在乘法等后续操作中,基于这样的语义进行对应的操作处理。这个协议显然是不安全的。


按照上一小节的介绍,我们只需要少量的修改相关代码即可实现在 Rosetta 中使用这个协议,完整的代码修改可以参考这里。具体的,类似于上面介绍的 SecureNN 中算子的实现,我们在 NaiveOpsImpl 类中实现内部逻辑的处理。其中实现隐私输入处理和“密文”下乘法操作的部分代码片段如下:


int NaiveOpsImpl::PrivateInput(int party_id, const vector<double>& in_x, vector<string>& out_x) {  log_info << "calling NaiveOpsImpl::PrivateInput" << endl;  int vec_size = in_x.size();   out_x.clear();  out_x.resize(vec_size);  string my_role = op_config_map["PID"];
// In this insecure naive protocol, we just half the input as local share. vector<double> half_share(vec_size, 0.0); for(auto i = 0; i < vec_size; ++i) { half_share[i] = in_x[i] / 2.0; }
msg_id_t msgid(_op_msg_id); if (my_role == "P0") { if (party_id == 0) { io->send(1, half_share, vec_size, msgid); } else if (party_id == 1) { io->recv(1, half_share, vec_size, msgid); } } else if (my_role == "P1") { if (party_id == 0) { io->recv(0, half_share, vec_size, msgid); } else if (party_id == 1) { io->send(0, half_share, vec_size, msgid); } } for(auto i = 0; i < vec_size; ++i) { out_x[i] = std::to_string(half_share[i]); } return 0;}
int NaiveOpsImpl::Mul(const vector<string>& a, const vector<string>& b, vector<string>& output, const attr_type* attr_info) { log_info << "calling NaiveOpsImpl::Mul" << endl; int vec_size = a.size(); output.resize(vec_size); for (auto i = 0; i < vec_size; ++i) { output[i] = std::to_string((2 * std::stof(a[i])) * (2 * std::stof(b[i])) / 2.0); } return 0;}
复制代码


而在框架集成方面,只需要在ProtocolManger中添加一行协议注册代码:


REGISTER_SECURE_PROTOCOL(NaiveProtocol, "Naive");
复制代码


在完成上述简单代码修改后,我们重新编译整个 Rosetta 代码库,就可以把这个协议集成进来了!完全不需要修改任何 TensorFlow 相关的代码。下面让我们运行一个上层 demo 验证下效果,在这个 demo 中,我们直接在“密文”上计算 P0P1 隐私数据的乘积:


#!/usr/bin/env python3
# Import rosetta packageimport latticex.rosetta as rttimport tensorflow as tf
# Attention! # This is just for presentation of integrating a new protocol.# NEVER USE THIS PROTOCOL IN PRODUCTION ENVIRONMENT!rtt.activate("Naive")
# Get private data from P0 and P1matrix_a = tf.Variable(rtt.private_console_input(0, shape=(3, 2)))matrix_b = tf.Variable(rtt.private_console_input(1, shape=(3, 2)))
# Just use the native tf.multiply operation.cipher_result = tf.multiply(matrix_a, matrix_b)
# Start executionwith tf.Session() as sess: sess.run(tf.global_variables_initializer()) # Take a glance at the ciphertext cipher_a = sess.run(matrix_a) print('local shared matrix a:\n', cipher_a) cipher_result_v = sess.run(cipher_result) print('local ciphertext result:\n', cipher_result_v) # Get the result of Rosetta multiply print('plaintext result:\n', sess.run(rtt.SecureReveal(cipher_result)))
复制代码


P0P1P2 分别在终端中指定自己的角色后启动脚本,并根据提示输入自己的隐私数据,比如P1可以输入自己的隐私数据为 1~6 的整数:



那么我们可以得到如下的运行结果(这里我们假设P0 输入的也是 1~6 的整数):



bravo!从结果中可以看出,系统通过调用 Naive 协议的后端算子来完成 TensorFlow 中相关 API 的计算,使得隐私输入、中间计算结果都是以“密文”的形式均分在P0P1 手中。而在最后也可以恢复出“明文”计算结果。


其它相关的get_supported_protocols 等 API 此时也可以感知到这个新注册的后端协议:


小结

在本篇文章中,我们介绍了 Rosetta 中是如何通过引入一个中间抽象层组件,来使得后端隐私协议开发完全和上层 AI 框架相解耦的。对于密码学专家等开发者来说,只要参考我们这里介绍的示例协议,在很短的时间内,就可以快速的将自己新设计的安全协议引入到上层 AI 场景应用中来。


本文中介绍的是一个用于协议集成演示的不安全的协议,至于如何真正的集成一个业界前沿的密码学 MPC 协议,并进行面向生产环境落地的高性能改造,以使得用户的隐私数据在整个计算过程中安全的流动,我们会在下一篇文章中具体阐述。stay tuned!


作者介绍:


Rosetta 技术团队,一群专注于技术、玩转算法、追求高效的工程师。Rosetta 是一款基于主流深度学习框架 TensorFlow 的隐私 AI 框架,作为矩阵元公司大规模商业落地的重要引擎,它承载和结合了隐私计算、区块链和 AI 三种典型技术。目前 Rosetta 已经在 Github 开源(https://github.com/LatticeX-Foundation/Rosetta) ,欢迎关注并参与到 Rosetta 社区中来。


参考资料:


[1] 隐私 AI 框架 PySyft: https://github.com/OpenMined/PySyft


[2] Martin, Robert C. Agile software development: principles, patterns, and practices. Prentice Hall, 2002.


系列文章:


隐私 AI 工程技术实践指南:整体介绍


面向隐私AI的TensorFlow深度定制化实践


2020-10-12 16:123485

评论 2 条评论

发布
用户头像
勘误:“SecureNN 中的mpc_t 类型就是 uint64_t (如果用户配置了使用 128 位的大整数,则是 uint128_t)。因为很多密码学的基础操作都需要在抽象代数的环(ring)、域(filed)上进行(同时,最新 SecureNN 等 MPC 协议又为了充分利用基础硬件的运算加速,已经支持直接在整数环
\Z264
上进行运算处理),所以转换到大整数上几乎是所有相关隐私技术必要的内部操作。” 中的"域(filed)" 应该是"域(field)",“\Z264 ” 排版上有些问题,应该是“$Z_{2^{64}}$”.
2020-10-12 16:35
回复
正文已更正,忽略此评论~
2020-10-12 18:27
回复
没有更多了
发现更多内容

“Pandabuy事件后,淘宝代购集运系统如何强化仿牌敏感词风控策略“

tbapi

淘宝代购集运系统 Pandabuy 逆向海淘系统

数据可视化在石油新能源行业的应用:深度探索与前沿趋势

不在线第一只蜗牛

数据挖掘 数据分析 低代码 数据可视化

高性能存储 SIG 月度动态:优化 xfs dax reflink 时延,独立选型并维护 mdadm 和 ledmon

OpenAnolis小助手

操作系统 高性能存储 龙蜥社区SIG

轻松应用 RapidMiner 内置案例模板实现数据挖掘详解(下篇)

Altair RapidMiner

人工智能 数据挖掘 算法 数据分析 altair

高性能网络SIG月度动态:virtio技术委员会通过flow director提案,netdim调节特性正式合入上游社区

OpenAnolis小助手

操作系统 龙蜥社区 龙蜥社区SIG

22 位委员参会,第 25 次龙蜥社区运营委员会圆满结束

OpenAnolis小助手

操作系统 龙蜥社区

本周五开讲!AI 时代的运维开发工具 OS Copilot 陪跑班,分享云上最佳实践案例

OpenAnolis小助手

Alibaba Cloud Linux OS Copilot 运维开发工具

天润融通微藤大模型,如何助力市场部构建企业增长飞轮?

天润融通

ISP代理与住宅代理的主要区别

IPIDEA全球HTTP

技术 ISP 代理IP

【YashanDB知识库】存储过程报错snapshot too old

YashanDB

yashandb 崖山数据库 崖山DB

SD-WAN能否优化SaaS访问体验?

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

Web 开发者必备:最推荐的工具清单

Liam

程序员 前端 Web

从IDC数据中心到云再到智算中心,苏州IDC决胜算力新时代

苏州服务器托管

数据中心

苏州八大行业服务器托管方案分享?IDC机房选择经验

苏州服务器托管

算力 IDC 服务器托管

Databend 完美适配 KubeSphere 企业版 4.1.1,让云原生技术更普及

Databend

破局移动影像,华为的化境是绝无止境

脑极体

AI

腾讯特别调薪8%,年底十三薪分摊到月薪:福利升级还是另有深意?

王中阳Go

腾讯 面经

软件测试学习笔记丨Cookie处理

测试人

软件测试

直播预约丨《指标体系建设实战》第四期:如何构建全面的指标管理体系

袋鼠云数栈

大数据 指标体系 指标管理 指标中台 指标建设

2024中国PMO高峰论坛在京成功召开

财见

告别 CentOS,开源操作系统与时代同步更需“根”的力量

OpenAnolis小助手

操作系统 龙蜥社区 CentOS 停服

拼多多商品详情数据接口全解析:获取商品信息的高效途径

tbapi

拼多多 拼多多商品详情数据接口 拼多多API 拼多多商品数据采集

阶跃星辰启动「繁星计划」开放平台;运动迁移框架 MotionClone 无需训练,一键克隆视频运动丨 RTE 开发者日报

声网

Cloud Kernel SIG 月度动态:发布 ANCK 3 个版本,5.10 kABI/kAPI 策略变更

OpenAnolis小助手

操作系统 龙蜥社区 龙蜥社区SIG Cloud Kernel

Linux多线程

不在线第一只蜗牛

Linux 运维 多线程 服务器

线上观看 3 万+!「智能可观测运维技术MeetUp」精彩回顾,探讨智能体构建新方向

OpenAnolis小助手

操作系统 龙蜥社区 龙蜥meetup 可观测技术

【YashanDB知识库】字段加上索引后,SQL查询不到结果

YashanDB

yashandb 崖山数据库 崖山DB

说说RabbitMQ延迟队列实现原理?

王磊

Java 面试

隐私AI框架中MPC协议的快速集成_开源_Rosetta技术团队_InfoQ精选文章