在上一篇文章中,我們介紹了,對於安全技術開發者,如何快速的基於 Rosetta 等隱私 AI 框架所提供的一系列介面,將自己的安全協議整合落地到上層的 AI 應用中來。在這一篇文章中,我們將介紹為了保護使用者的隱私資料,在隱私 AI 框架的計算任務全流程中,資料是如何以密文形式流動,同時仍正確完成加法、乘法等計算步驟的。
隱私 AI 系統存在的目的就是賦能 AI,使得各種 AI 場景下對使用者隱私資料的使用都是安全的。那麼,這樣的系統就需要提供充分的保障,從理論到工程實現的每一個階段都應該是經得起推敲、抵抗得住各種攻擊的。不能簡單的認為只需要各方先在本地自己的資料上計算出一個模型,然後將模型結果交換一下計算下其模型引數的平均值,就不會洩露各方的隱私資料了。現代密碼學(Cryptography)是建立在嚴格的數學定義、計算複雜度假設和證明基礎之上的,其中 MPC (Multi-Party Computation)方向是專門研究多個參與方如何正確、安全的進行聯合計算的子領域, Rosetta 、 TF Encrypted 等隱私 AI 框架都採用了 MPC 技術以提供可靠的安全性。下面我們就結合具體案例看的看下在 Rosetta 中隱私資料是如何得到安全保護的。
案例
Alice, Bob 和 Charley 三人最近需要在他們的 AI 系統中引入對資料的隱私保護能力。他們很重視安全性,所以他們想透過一個簡單的例子 —— 乘法(multiply),來驗證下隱私 AI 框架是否真正做到了隱私安全。
他們約定:Alice 的輸入為 1.2345;Bob 的輸入為 5.4321;而 Charley 拿到相乘的結果。
他們按照 Rosetta 提供的教程,快速編寫了如下程式碼(指令碼名為 rosetta-mul.py):
#!/usr/bin/env python3import latticex.rosetta as rttimport tensorflow as tf
rtt.activate("SecureNN")
x = tf.Variable(rtt.private_console_input(0, shape=(1,))) # Alice's inputy = tf.Variable(rtt.private_console_input(1, shape=(1,))) # Bob's inputz = tf.multiply(x, y) # z = x * ywith tf.Session() as sess:
sess.run(tf.global_variables_initializer())
res = sess.run(rtt.SecureReveal(z, receive_party=4)) # 'b0100' means Charley
print('z:', res)
rtt.deactivate()
接著,他們各自開啟一個終端,分別進行如下操作:
Alice (P0) 在終端敲下如下命令列,並根據提示輸入自己的私有資料:
$ python ./rosetta-mul.py --party_id=0
please input the private data (float or integer): 1.2345
Bob (P1) 執行同樣的操作,輸入的也是隻有自己才知曉的私有資料:
$ python ./rosetta-mul.py --party_id=1
please input the private data (float or integer): 5.4321
Charley (P2) 在終端敲下如下命令列,等一會兒就可以得到所期望的計算結果:
$ python ./rosetta-mul.py --party_id=2z: [b'6.705910']
注:
對於 Charley 來說,由於沒有資料,故不會提示輸入。Alice 和 Bob 的本地輸出都會是 z: [b'0.000000'], 而不會拿到正確的結果。
可以看出,Charley 拿到的最終結果與邏輯上明文結果(6.70592745)相比,誤差(0.00001745)可以忽略不計。 系統的正確性得到了驗證。
如果想讓誤差更小,可以透過配置提升精度,或使用 128-bit 的資料型別。具體操作可以參考文件。
上面短短的幾行程式碼,雖然結果是正確的,但是他們三人對於系統安全性方面仍有一些困惑:
- Alice、Bob 的私有輸入會不會被另外兩方知道?
- Charley 拿到的結果會不會被 Alice 、 Bob 知道?
- 整個程式執行過程中,有沒有其他資料的洩漏?
- 如果前幾問的回答都是否定的,那 Charley 又是如何得到明文?
在回答這些問題之前,為簡化描述、突出本質,我們需要簡單介紹一下 MPC 實際落地中常用的安全假設。
假定系統中有 3 個節點,P0/P1/P2,其中 P0/P1 為資料參與方,而P2 是輔助節點,用於隨機數生成。
- 只考慮半誠實(Semi-Honest)安全模型,即三方都會遵守協議執行的流程,並且其中誠實者佔多數(Honest-Majority),也就是說三個節點中只會有一個“壞人”。更為複雜的惡意(Malicious)模型等安全場景可參考其他相關論文。
- 內部資料型別為 64 位無符號整型,即 uint64_t (這在理論上對應著在環 Z264Z264 )。
下面我們就按照 輸入–計算–輸出 的順序,詳細介紹 Rosetta 中資料的表示與流動,以及所用到相關技術與演算法在工程上的最佳化實現。
隱私資料的輸入
隱私計算問題,首先要解決的是隱私資料的輸入。
在上述案例中,是透過如下兩行程式碼來完成私有資料的安全輸入的:
x = tf.Variable(rtt.private_console_input(0, shape=(1,))) # Alice's inputy = tf.Variable(rtt.private_console_input(1, shape=(1,))) # Bob's input
如程式碼所示,rtt.private_console_input 是 Rosetta 眾多 API 之一,Rosetta 還提供了 rtt.private_input、rtt.PrivateDataset 等 API,用來處理隱私資料的輸入。
這裡發生了什麼?x,y 的值會是什麼?
在進入剖析之前,先來了解一個重要的概念 —— 秘密分享。
秘密分享
什麼是 秘密分享(Secret Sharing)[1]?先來看一種簡單的兩方 additive 的構造:
給定一個數 x,定義 share(x) = (x0, x1) = (x − r, r),其中 r 是隨機數,並且與 x 獨立。可以看到,x = x0 + x1 = x -r + r。原始資料 x 的秘密分享值 (x0, x1) 將會由兩個資料參與方 (P0, P1) 各自儲存。
在秘密分享的方案中,所有的資料,包括中間數值都會分享在兩個參與方之間。直觀的看,參與的兩方不會得到任何的明文資訊。理論上,只需要在環 Z264Z264 上支援加法和乘法,就可以進一步透過組合來支援上層各種更復雜的函式。下文我們會看到關於乘法的詳細解析,讀者可以回過頭來再看這段話。
那工程上是如何處理的呢?假設 P0 有一個私有資料 x,三方之間可以簡單互動一下實現:
方案 1: P2 生成一個隨機數 r,傳送給 P0/P1。然後 P0 本地設定 x0 = x - r,P1 本地設定 x1 = r。此時 share(x) = (x0, x1) = (x - r, r),與上面的定義一致。
這是方案之一,後文基於這個方案進行講解。在本文最後,我們會提一下實際落地時的進一步最佳化方案。
回到主線,結合 方案 1,我們以 Alice 的輸入值 x = 1.2345 為例看下具體過程。
第一步,浮點數轉定點數。
對於有資料輸入的一方,需要先將真實的浮點數轉成定點數。
浮點數轉定點數:即浮點數乘以一個固定的縮放因子(scale,一般為 2 k2k )使其變成定點數( 取其整數部分,捨棄小數部分)。當最後需要還原真實的原始浮點數時,用定點數除以縮放因子即可。
我們先選定一個縮放因子,這裡取 scale = 218218 。
經過縮放後,得到 x = 1.2345 * (1<<18) = 323616.768,取整數部分即 323616,用於後續計算。
這裡是有精度損失的,但在精度允許的範圍內,不會影響後面的計算。
第二步,生成隨機數 r。 也叫掩碼(Masking value),用來隱藏真實值。
P2 生成一個隨機數 r,是一個 64-bit 空間的無符號整數。(如,r = fdad72ecbfbefeb9。這裡用十六進位制表示,下同)
P2 將 r 傳送給 P0,P1。此時,P0,P1 都擁有一個相同的隨機數 r 了。
第三步,設定秘密分享值。
按秘密分享 方案 1 的定義,P0 本地設定 x0 = x - r,P1 本地設定 x1 = r。所以有:
P0:x0 = 4f020 - fdad72ecbfbefeb9 = 2528d134045f167
P1:x1 = fdad72ecbfbefeb9
4f020 是 323616 的十六進位制表示。
如果計算結果大於 64 bit 的空間範圍,需要對結果取模(64 位空間)。下同。
至此,我們獲得了關於 x 的分享值,share(x) = (x0, x1) = (x - r, r) = (2528d134045f167, fdad72ecbfbefeb9)。
感興趣的讀者朋友可以驗證一下 (x0 + x1) mod 264264 。
同理,我們對 Bob 的輸入值進行一樣的處理。
上面三步,其實就是 private_console_input 的工作機制與過程。經過處理後,各方本地都儲存著如下值:
我們用 Xi,Yi 表示 X,Y 在 Pi 的分享值。(下同)
P2 為何是 0 呢?在本方案中 P2 作為一個輔助節點,不參與真正的邏輯計算。
我們可以看到,在處理隱私資料輸入的整個過程中,P0 無法知道 y 值,P1 無法知道 x 值,P2 無法知道 x 或 y 值。
最後,我們總結一下這三個主要階段:
密文上的協同計算
這是計算的核心。
上一步,我們 保證了輸入的安全,沒有資訊的洩漏,各方無法單獨拿到其他節點的私有輸入。
接下來,看一下計算的程式碼,只有一行:
z = tf.multiply(x, y) # z = x * y
在這個計算的過程中發生了什麼?下面結合乘法(Multiply) 運算元原理進行例項講解。
Multiply 運算元原理
乘法相對較為複雜,我們結合經典的 Beaver Triple 方法 [2] 加以介紹。該方法是由密碼學家 Donald Beaver 在 1991 年提出,主要思想是透過乘法。協議基本原理如下:
輸入: P0 擁有 X,Y 的秘密分享值 X0,Y0;P1 擁有 X,Y 的秘密分享值 X1,Y1。
這裡有 share(X) = (X0, X1),share(Y) = (Y0, Y1)。在此案例中,這裡的輸入,就對接上一節所述的"隱私輸入"的輸出結果。
計算步驟:
- P2 本地生成一組隨機數 A,B,C,且滿足 C = A * B。
A,B,C 的生成步驟:P2 隨機生成 A0,A1,B0,B1,C0,令 A = A0 + A1, B = B0 + B1,得到 C = A * B, C1 = C - C0。 其中 Ai,Bi,Ci 是 A,B,C 的分享值。 這些都是在 P2 本地完成的。這也就是 P2 做為輔助節點的作用(用於隨機數生成)。
這裡 A,B,C 一般被稱為三元組(Beaver’s Triple)。
比如,某次生成的隨機數如下:
感興趣的讀者朋友可以驗證一下。如 A = A0 + A1 = 2373edde1a0e5dcd + ad483b77e4e5db41 = d0bc2955fef4390e。這個 A 是個 隨機數。
- P2 將上一步生成的隨機數的秘密分享值分別傳送給 P0,P1。
即將 A0,B0,C0 傳送給 P0;將 A1,B1,C1 傳送給 P1。
此時,P0,P1 擁有如下值:
- P0 計算 E0 和 F0;P1 計算 E1 和 F1。並交換 Ei,Fi。
P0 本地計算 E0 = X0 - A0, F0 = Y0 - B0。
P1 本地計算 E1 = X1 - A1, F1 = Y1 - B1。
此時,P0,P1 擁有如下值:
交換 Ei,Fi。
P0 將 E0,F0 傳送給 P1;P1 將 E1,F1 傳送給 P0。此時,P0,P1 都擁有 E0,F0,E1,F1。
- P0,P1 本地計算 得到E 和 F。
P0,P1 各自本地計算 E = E0 + E1, F = F0 + F1。
- P0 本地計算 Z0;P1 本地計算 Z1。
現在,P0 已經擁有 A0,B0,C0,E,F;P1 已經擁有 A1,B1,C1,E,F。有了這些就 可以計算出 Z = X * Y 的秘密分享值 Z0,Z1 了。 即:
P0 本地計算 Z0 = E * B0 + A0 * F + C0;
P1 本地計算 Z1 = E * B1 + A1 * F + C1 + E * F。
正確性可以透過以下恆等式加以驗證:
Z= Z0+ Z1 = E⋅ B0+ A0⋅ F+ C0+ E⋅ B1+ A1⋅ F+ C1+ E⋅ F
= E⋅( B0+ B1)+( A0+ A1)⋅ F+( C0+ C1)+ E⋅ F
= E⋅ B+ A⋅ F+ C+ E⋅ F = E⋅ B+ A⋅ F+ A⋅ B+ E⋅ F
=( A+ E)⋅( B+ F) =( A+ X− A)⋅( B+ Y− B)= X⋅ Y Z
=Z0+Z1
=E⋅B0+A0⋅F+C0+E⋅B1+A1⋅F+C1+E⋅F
=E⋅(B0+B1)+(A0+A1)⋅F+(C0+C1)+E⋅F
=E⋅B+A⋅F+C+E⋅F
=E⋅B+A⋅F+A⋅B+E⋅F
=(A+E)⋅(B+F)
=(A+X−A)⋅(B+Y−B)=X⋅Y
輸出: P0,P1 分別持有 Z = X * Y 的秘密分享值 Z0,Z1。
我們可以看到, 從輸入到計算再到輸出,整個過程中,沒有洩漏任何隱私資料。
整個運算元計算結束時, P0 單獨擁有 X0,Y0,A0,B0,C0,E0,F0,E1,F1,E,F,Z0,P1 單獨擁有 X1,Y1,A1,B1,C1,E0,F0,E1,F1,E,F,Z1,P2 單獨擁有 A0,B0,C0,A1,B1,C1,三方都 無法單獨得到 X 或 Y 或 Z。
最後,我們總結一下(1~5 對應著上面的步驟):
獲取明文結果
從 輸入到計算再到輸出,各節點只能看到本地所單獨擁有的秘密分享值(一些毫無規律的 64 位隨機數),從上文也可以看到,節點是無法獨自得到任何隱私資料的。那麼,當計算結束後,如果想得到結果的明文,怎麼獲取呢?
Rosetta 提供了一個恢復明文的介面,使用很簡單。
res = sess.run(rtt.SecureReveal(z, receive_party=4)) # 'b0100' means Charleyprint('z:', res)
那這個 rtt.SecureReveal 是如何工作的呢?
其實非常簡單:如果本方想要知道明文,只需要對方把他的秘密分享值發給我,然後本地相加即可。
比如,上一節結尾處,我們知道了各方 (P0/P1/P2) 的分享值如下:
結合例項,Charley (P2) 想要獲取結果明文,那麼,P0,P1 將各自的分享值 Z0,Z1 發給 P2,然後 P2 本地相加即可。即: 1db35d14c12a + ffffe24ca30611b0 = 1ad2da (1757914)。我們將此值進一步再轉換為浮點數,就可以得到我們想要的使用者態的明文值了: 1757914 / (1 << 18)= 6.7059097 !
效率最佳化
上面介紹的 秘密分享 與 multiply 運算元 都是基於最基本的演算法原理,在時間上和通訊上的開銷還是比較大的,在實際工程實現中,還可以進一步進行最佳化以提升效能。在介紹最佳化方案之前,先了解一下 什麼是 PRF?
PRF[3],Pseudo-Random Function。 簡單來說,即給定一個隨機種子 key,一個計數器 counter,執行 PRF(key, counter) ,會得到一個 相同的隨機數。
補充說明一下。例如,P0,P1 執行 PRF(key01, counter01),會產生一個相同的隨機數。這裡的 key01,counter01 對於 P0,P1 來說是一致的。程式會維護這兩個值,對使用者來說是透明的。
關於秘密分享
來看看最佳化版本(假設: P0 有一個私有資料 x)。
- 方案 2(最佳化版): P0,P1 使用 PRF(key01, counter) 本地同時生成一個 相同的隨機數 r。然後 P0 本地設定 x0 = x - r,P1 本地設定 x1 = r。此時 share(x) = (x0, x1) = (x - r, r),與定義一致。 此版本無需 P2 的參與。沒有通訊開銷。
目前 Rosetta 開源版本中使用的正是此方案。
- 方案 3(最佳化版): P0 設定 x0 = x,P1 設定 x1 = 0 即可。此時 share(x) = (x, 0)。 此版本無需 P2 的參與。沒有通訊開銷,也沒有計算開銷。
上述各個版本(包括 方案 1),都是 可行且安全的。可以看到P1/P2 並不知道 P0 的原始輸入值。
關於 Multiply 運算元
Multiply 運算元中輸入,輸出部分沒有變化,主要是計算步驟中的第 1 步與第 2 步有些許變化,以 減少通訊量。這裡也只描述這兩步,其餘與前文相同。
在上文的描述中,我們知道 P2 生成三元組後,需要將其分享值傳送給 P0,P1。當我們有了 PRF 後,可以這麼做:
- P0,P2 使用 PRF(key02, counter02) 同時生成 A0,B0,C0;P0,P1 使用 PRF(key12, counter12) 同時生成 A1,B1。
- 然後,P2 令 A = A0 + A1, B = B0 + B1,得到 C = A * B, C1 = C - C0。P2 將 C1 傳送給 P1。P2 的任務完成。目前 Rosetta 開源版本中使用的是此方案。
相比於原始版本,最佳化版本只需要傳送 C1,不用再傳送 A0,A1,B0,B1,C0。
在 Rosetta 中,我們還針對具體的各個不同的運算元進行了一系列的演算法、工程最佳化,歡迎感興趣的讀者來進一步瞭解。
小結
安全性是隱私 AI 框架的根本,在本篇文章中,我們結合隱私資料輸入的處理和密文上乘法的實現,介紹了“隨機數” 形式的密文是如何在多方之間流動,同時“神奇”的仍能保證計算邏輯的正確性的。我們這裡對於安全性的說明是直觀上的描述,而實際上,這些演算法的安全性都是有嚴格數學證明的。感興趣的讀者可以進一步去探索相關的論文。
Rosetta 將持續整合安全可靠的密碼學演算法協議作為“隱私計算引擎”到框架後端中,也歡迎廣大開發者參與到隱私 AI 的生態建設中來。
參考文獻
[1] 秘密共享,
[2] Beaver, Donald. “Efficient multiparty protocols using circuit randomization.” Annual International Cryptology Conference. Springer, Berlin, Heidelberg, 1991.
[3] PRF 的一個簡單介紹,
作者介紹:
Rosetta 技術團隊,一群專注於技術、玩轉演算法、追求高效的工程師。Rosetta 是一款基於主流深度學習框架 TensorFlow 的隱私 AI 框架,作為矩陣元公司大規模商業落地的重要引擎,它承載和結合了隱私計算、區塊鏈和 AI 三種典型技術。目前 Rosetta 已經在 Github 開源( ) ,歡迎關注並參與到 Rosetta 社群中來。
延伸閱讀: