1. 程式人生 > >匿名鏈中的密碼學

匿名鏈中的密碼學

以比特幣起始的各類數字貨幣,對應的賬號只是一串數字,而這串數字背後是誰在操控則無從所知。然而由於交易流向、交易金額都是全網公開的,這就給了大資料分析和追蹤的機會。

其實比特幣在設計之初也考慮過賬戶與交易關聯的匿名性,因為整個比特幣的賬戶地址幾乎是無限的,大家都可以隨意建立錢包地址,這樣你甚至可以做到每筆交易都拿一個新地址來接收。對於這些分散的交易你可能需要整合才能進行一筆較大的交易,而這種多輸入的交易一旦出現,我們就可以將這些來源地址劃分到同一賬戶下,繼而對這些賬戶進行進一步追蹤,這樣通過對整個交易網路的分析我們就能得到很多可關聯的資訊。加上交易金額也是公開的,對於一些特定金額的交易我們也能找到蛛絲馬跡,如果對應地址的身份得到確認,通過這些資訊就可以推算出更多的地址資訊。另外還有找零所使用的零錢地址也會帶來資訊的暴露,早期比特幣的客戶端就出現過零錢地址總是在輸出地址的第一個的bug,通過這樣層層遞進我們就可以揭開那些隱藏在比特幣網路下的真實身份。

同時如果你在外使用比特幣支付那麼別人也能看到你的賬戶裡有多少錢,怎麼想這都不太安全。

對於企業而言區塊鏈的隱私性也是非常重要的,比如某些在區塊鏈上與對方簽訂的合約,以及跟客戶的交易資訊等等,很多應該算是商業機密了,這些都需要得到妥善保護。

以上種種,催生了一波所謂匿名幣,幾個知名匿名幣介紹可參看 簡析主流匿名幣:Dash、門羅幣、Zcash、SERO。本文大部分內容來自對門羅的研究。

 

在數學領域,人們的直觀感覺是,隨著運算數的增大,除法比乘法的難度增長快,求對數比求冪的難度增長快,直至計算機都無法在多項式時間長度內得到正確的反向(除法、對數)運算結果。即:

\(r\xrightarrow{\times G} R, but\quad R\stackrel{\div G}\nrightarrow r\)

這個式子雖然簡單,但是當前主流密碼學的基石。雖然尚沒有嚴格的數學證明,目前流行的非對稱加密演算法如RSA和ECC,都是基於此客觀事實。


金鑰協商(key exchange)

我們請出密碼學中兩個知名的虛擬人物—— Alice 和 Bob ——來演示如何在眾目睽睽之下完成金鑰的協商。

以下 Alice 和 Bob 的對話是公開的。

Alice:我們先選出一個大數G,這個G所有人都可以知道。然後我在心裡面想一個大數a,我把a乘以G的結果,假設是A告訴Bob,當然A是多少也被大家聽到了。

現在大家包括Bob知道了G、A,而a只有Alice本人知道。

Bob:好的,現在我也在心裡想一個大數b,並且把b乘以G的結果B告訴Alice。

現在所有人都知道G、A、B,而a只有Alice本人知道,b只有Bob本人知道。

Alice:明白了,那麼最終敲定的金鑰就是a乘以B的結果,這個用計算機很快就能算出來。

Bob:算出來的結果不用告訴我,因為我拿b乘以A得到的結果是一樣的。
得到金鑰:aB=abG=baG=bA

以上是基於乘法的金鑰協商過程,基於求冪的公式就是\((g^{\color{red}a})^{\color{red}b}=(g^{\color{red}b})^{\color{red}a}\)。如果再加入一些數論中的概念,則可以得到如下式子(Diffie-Hellman金鑰協商演算法):

\((g^a\;mod\;p)^{b}\;mod\;p=(g^b\;mod\;p)^{a}\;mod\;p\),其中p是素數,g是p的本原根。

而ECC更進一步,將數域範圍縮小到某條橢圓曲線上,再\(\;mod\;p\)後取其中一個有限群,然後再取該有限群下的一個迴圈子群,這樣似乎破解難度更高。不過不管何種方式,所利用的原理無非是\(f(A,b)=f(B,a)\)。

維基百科上使用了一個很有趣的比喻——顏料的混合。設想這樣一個場景,Alice和Bob,他們想在不見面的情況下祕密約定出一種顏色,但他們互相溝通的資訊都會被公開,應該怎麼辦呢?

 

再來聊聊ECC的加解密,其實也用到了金鑰協商的概念。

假設m為待加密資訊,Alice擁有金鑰對(a,A),Bob擁有金鑰對(b,B),aG=A,bG=B;Alice要將m安全地傳遞給Bob。

  1. Alice將m編碼後計算:M=m+aB。
  2. Bob拿到M計算:m=M-bA,完成。

一般為了更安全計,通訊雙方使用的金鑰對是臨時生成,用過即銷燬。


stealth address

這回Alice要給Bob轉一筆錢,而Bob不希望其他人知道是他得到這筆錢,又該怎麼辦呢?

  1. Bob在鏈上公開了他的標準地址,這個地址不是直接用於收款的,而是儲存了兩個公鑰(\(B_1、B_2\)),任何人都能獲得,他自己則保留對應的兩個私鑰(\(b_1、b_2\))。\(B_1=b_1G,B_2=b_2G\),這裡G是橢圓曲線上選取的基點,全網統一,同樣的演算法基點都是一樣的.
    門羅標準地址格式:網路編碼(1 byte)+public spend key(32 byte)+public view key(32 byte)+校驗和(4 byte)
  2. Alice隨機生成了一個整數r作為另一個私鑰,套用公式\(P=H_s(rB_1)G+B_2\)得[一次性]公鑰P,其中\(H_s\)是一個雜湊函式。P就是Bob的一次性收款地址(stealth address)。
    思考\(H_s\)的作用?
  3. Alice使用P作為轉賬的目標地址,同時將\(R=rG\)打包進此次交易。注意她可以使用相同的r進行不同目標地址的轉賬。
  4. Alice將交易傳送至鏈,因此P、R也一同被公開到鏈上。
    P中的\(H_s\)不但規範了P的資料結構(長度),同時也避免第三方將P與Bob關聯起來。試想,若無\(H_s\),那麼\(P=rB_1G+B_2=RB_1+B_2\),第三方很容易通過試算得到實際的收款方。
  5. Bob掃描區塊鏈上[自上次掃描以來的]最近交易,計算\(P'=H_s(b_1R)G+B_2\)。如果P’=P,則說明這筆轉賬是給Bob的,因為\(b_1R=b_1rG=rb_1G=rB_1\)。
  6. Bob要花這筆錢(轉賬給別人),則首先要計算出與P對應的私鑰x,即xG=P,由\(P=P'=H_s(b_1R)G+B_2\)可得:\(x=H_s(b_1R)+b_2\)。這個私鑰連給錢的Alice也無法推出(因為有\(b_2\)項),日後Bob就可以使用這個私鑰來花這筆錢了。

環簽名(Ring Signatures)

使用多個公鑰及其中一個對應私鑰,構建一個正確的方程(在不知其中任意一個私鑰的情況下很難構建這樣的方程)。在別人看來,你必是知道其中某個公鑰的私鑰,但是無法推定你知道哪一個。演算法有很多,關鍵是構造的方程形式,舉例:

驗證方要求構建的方程形如\(f(\mathbb{P};\mathbb{X})=g_1(P_1,x_1)+g_2(P_2,x_2)+\cdots +g_n(P_n,x_n)=v\),其中\(v\)是驗證方隨意取的一個大數,\(\mathbb{P}\)是[證明方選取的]公鑰集合,\(\mathbb{X}\)是[證明方給出的]值集合,\(g_i\)是\(P_i\)對\(x_i\)的加密函式(為了方便表述,得到的結果記為\(y_i\))。

在不知道任何一個私鑰的情況下,得到合適的\(\mathbb{X}\)基本不可能,而只要擁有其中一個私鑰,這就不是問題。假設我們擁有\(P_s\)的私鑰,給除\(x_s\)之外的所有\(x_i\)隨意賦值,得到除\(y_s\)之外的所有\(y_i\),要使等式成立,則\(y_s=v-\sum\limits_{i=1,i\not=s}^ny_i=g_s(P_s,x_s)\),由於\(g_s\)是對\(x_s\)的加密,只要拿私鑰解密即可得到合適的\(x_s\),補足了\(\mathbb{X}\)。

證明方將\(\mathbb{P}\)、\(\mathbb{X}\)發給驗證方,驗證方驗算即可。

上述過程需要驗證方事先給出\(v\),未免麻煩。有人想到了這個\(v\)可以由證明方設定,前提是\(v\)要參與到式子左邊的計算,計算完成之後又得到它本身,首尾相等,便是所謂的環簽名。下面是monero中Adam Back提出的一個演算法(值得一提的是,這個演算法並非Adam Back所創,提出這個演算法的是Joseph K. Liu, Victor K. Wei, and Duncan S. Wong,看名字似乎是中國人):

  1. 隨機選取n-1個公鑰,連同自己的公鑰\(P_s\)組成n個公鑰集合\(\{P_1,P_2,\cdots ,P_s\cdots ,P_n\}\);
  2. 隨機選取n-1個值\(\{x_1,x_2,\cdots ,x_n\}\),其中\(x_s\)暫缺,後面會使用計算得到的合適值補上;
    這兩步和之前的例子一樣。
  3. 設一未知數\(v_1\),再設\(Y_1=x_1G+v_1P_1\);
  4. 設一未知數\(l\),再設\(R_1=x_1H_p(P_1)+v_1l\);
    注意\(l,R_i\)存在與否並不影響環簽名,而是另有考慮,可看下節Key Image,此節可忽略。
  5. 計算\(v_2=H_s(m,Y_1,R_1)\),其中\(H_p\)和\(H_s\)是兩個不同的雜湊函式,m是需要簽名的資料,根據業務需求來;
  6. 同上依次計算\(Y_2=x_2G+v_2P_2;R_2=x_2H_p(P_2)+v_2l;v_3=H_s(m,Y_2,R_2);\cdots ;Y_n=x_nG+v_nP_n;R_n=x_nH_p(P_n)+v_nl;v_{n+1}=H_s(m,Y_n,R_n)\);
  7. 令\(\underline{v_{n+1}=v_1}\),這就是我們要構造的方程,為了使方程成立,關鍵落在尋找合適的\(x_s\)及未知數\(v_1,l\)上;
  8. 雖然每一環的\(v\)由上一環而來,但是由於其首尾相連,我們無法由任一環推出其值,也就是說其值是不定/任意的;那麼我們可以先隨意設定某環的計算結果,再反推出符合該結果的\(v\)值。因此任選一個數a,使得\(Y_s=aG,R_s=aH_p(P_s)\),\(Y_s,R_s\)一旦確定,\(v_{s+1}=H_s(m,Y_s,R_s)\)也跟著確定下來,繼而\(v_{s+2},v_{s+3},\cdots ,v_n,v_1,\cdots ,v_{s}\)都可依次推得;
  9. 又\(Y_s=x_sG+v_sP_s\),易得\(x_s=a-v_ss_s\),其中\(s_s\)是\(P_s\)對應的私鑰。如此,\(\mathbb{X}\)被補足,現在只剩\(l\)未知;
  10. 由\(R_s=x_sH_p(P_s)+v_sl=aH_p(P_s)\)及\(a=x_s+v_ss_s\),易得\(l=s_sH_p(P_s)\)。
  11. 證明方將\(\mathbb{P}\)、\(\mathbb{X}\)、\(v_1\)、\(l\)公開到鏈上,作為交易的發起者;礦工驗證。

Key Image

細心的讀者會發現,把上節中的\(l,R_i\)在環簽名過程中去掉,似乎也不影響簽名的有效性。再看最終得到的\(l=s_sH_p(P_s)\),很明顯\(l\)的值避開了簽名過程中產生的各種變數,只跟發起方的金鑰對有關,這使得\(l\)是個確定的數值,無法捏造。顯然\(R\)的公式是經過精心設計的。那麼monero把它們放在環簽名中應該也是刻意為之,作用是為了防止雙花。

雙花的概念是數字貨幣獨有的,而具體細節不同的數字貨幣又有所不同。

在比特幣中,Alice 將存在於自己同筆UTXO中的1個BTC在相隔很短的時間內轉賬給Bob和Tom,一部分礦工先收到了給Bob的那筆交易\(T_b\),一部分礦工先收到給Tom的那筆交易\(T_t\)。每個礦工都會檢查UTXO是否未被花費,也就是本節點資料庫和待打包交易池中不存在該UTXO,因此礦工要麼打包\(T_b\),要麼打包\(T_t\)。假設其中一個打包\(T_b\)的礦工出塊成功,那麼收到新塊通知的礦工會將本地交易池中的\(T_b\)和\(T_t\)移除。萬一有打包\(T_t\)的礦工未收到新塊通知,並且也在同一高度出塊成功,那麼就形成了分叉。依據比特幣的最長鏈規則,除非兩方的算力一直相同(各佔50%,這種情況即使出現也會很快失去平衡),否則總會是某條鏈勝出。這樣最終就概率性解決了雙花問題。

monero也使用UTXO,畢竟匿名性就決定了交易/餘額不能基於賬戶模型,但與比特幣不同,由於環簽名的緣故,礦工無法確定一個交易中的實際UTXO是哪個,自然無法將其從資料庫中標記刪除,從這個角度說,UTXO一旦產生,就永遠有效,這樣顯然不合理。門羅幣使用Key Image解決這個問題。簡單來說,一個Key Image對應一個UTXO(UTXO被花費的同時產生對應的Key Image),礦工只要檢視這次交易產生的Key Image是否存在,即可判斷UTXO是否被重複消費。上述的\(l\)就承擔了Key Image的職責。

隨著交易量的增長,Key Image的數量也會跟著膨脹,這會帶來一定的儲存問題和或者可能的檢索效率問題(在合適資料結構和不考慮儲存空間大小的情況下,檢索時間複雜度可一直維持在同一數量級)。

下面我們同樣以例子來梳理整個交易流程,以便更清晰的瞭解Key Image如何起作用。

回顧 stealth address 一節內容,monero的UTXO都是一次性地址,所以環簽名一節說到的公鑰集合\(\mathbb{P}\)其實都是一次性公鑰,而非標準公鑰。可知,環簽名中的\(s_s\)就是 stealth address 中的\(x=H_s(b_1R)+b_2\)。

Alice 在給 Bob傳送XMR的時候,其實是將自己的一筆或多筆UTXO變成Bob的一筆UTXO(當然一般還包括礦工費及找零,此處忽略);

而Alice的UTXO又是別人轉給她的,以任一筆來說,公鑰地址\(P_s=H_s(a_1R)G+A_2\),對應私鑰是\(s_s=H_s(a_1R)+a_2\),其中\(a_1,a_2\)是Alice的標準私鑰,R是上家給出的隨機數公鑰(參看stealth address 一節)。Alice持有\(s_s\)就表明可以合法使用這筆UTXO;

除了給出花費的UTXO 的真實地址\(P_s\)之外,還會隨機選取鏈上其它未花費的UTXO新增到交易的inputs中,並進行環簽名,環簽名用到了\(s_s\)。由\(P_s,s_s\)公式可知,只要上家給出的R不重複,Key Image也[幾乎]不會重複,而\(R=rG\),說到底防止雙花的關鍵落在上家給的隨機數\(r\)上,只要\(r\)位數夠長,且足夠隨機,那麼對同一個接收方(此處是Alice)來說重複的概率微乎其微。

2017年1月10日Monero中正式使用了RingCTs。而在此之前,Monero中假如Alice需要向Bob轉12.5個XMR,則需要將自己的UTXO分別傳送至三個地址,分別轉賬10XMR、2XMR和0.5XMR,這是因為Monero中要求轉賬的XMR格式為A×10BA×10B的格式,環簽名中誘騙的UTXO也要相同數額。使用了RingCTs之後,在區塊鏈上隱藏了交易數額,這時候Alice挑選mixins的時候,不用考慮mixins的XMR的餘額,提升了交易的隱私性。

Alice需要花費的真實UTXO有幾筆,該過程就重複幾次。

有些人會認為\(s_s\)只有發起方知道,如果發起方給出的\(l\)不合法也無從判斷。需要注意的是\(l\)並非發起方主動計算,而是在簽名過程中推匯出的結果,它的值是唯一的,且正好等於\(s_sH_p(P_s)\),如若捏造一個非法數值,將不會通過礦工的校驗。

結合以上所有內容,我們可以窺得並理解monero一次交易的全貌:


Ring Confidential Transactions

OK,目前為止,我們實現了在一次轉賬中:

  • 隱藏轉賬方[UTXO[一次性]公鑰地址](環簽名)。
  • 隱藏了收款方[標準公鑰地址](一次性地址)。
  • 防止雙花(Key Image)。

如果能把轉賬金額也給隱藏了,那就做到了完全匿名。外部無法知道誰轉給誰,轉了多少錢,但是這筆轉賬又是可驗證合法的。我們來考慮怎麼判斷轉賬金額沒問題。

  1. 首先,輸入=輸出。這很好理解,Alice支出的錢必須得等於(Bob收到的錢+礦工費+找零),收支平衡,只要保證貨幣初始來源合法(如挖礦),那麼追根溯源,後續的UTXO必然合法,然而這還不夠。
  2. 假如Alice構造了一筆交易輸入=1BTC,輸出=1000BTC+(-999)BTC,不考慮礦工費,其中1000BTC轉給自己的另一個地址,-999BTC轉給原地址作為找零,後續她可以不管原地址,而使用新地址,如此則憑空多出了鉅額財富。要避免這種情況很簡單,只要規定(每筆輸入輸出>0)。

滿足以上兩點,就可以判定交易金額沒問題。幸運的是,上述兩點並不要求轉賬的具體數額向驗證者公開,這就使隱藏金額有了可行性。那麼具體如何做呢?

同態加密

無需解密,就能夠對密文進行計算。在[計算者]計算完畢後[私鑰擁有者]再解密,可避免接觸到原始私密資料而得到正確結果。支援加法計算的叫加法同態(ECC),支援乘法的叫乘法同態(RSA),支援任意計算的則稱為全同態(基於理想格,2009,Gentry)。

Pedersen commitments

承諾場景讓你把一段資料作為私密儲存,但是要承諾它,使得你後來不能改變該資料。一個簡單的承諾場景用雜湊函式構建如:承諾=SHA256(盲化因子||資料)。如果你僅告訴別人承諾,別人沒法確定你承諾了什麼資料。但你後來揭露了盲化因子和資料,別人可以執行該雜湊函式來驗證是否與你以前的承諾相匹配。盲化因子必須存在,否則別人可以試圖猜測資料。如果你的資料比較少而簡單,猜測成功可能性比較大。

佩德森承諾與以上場景中的承諾類似,但是附加一個特性:承諾可以相加,多個承諾的總和等於資料總和的承諾。即加法同態:\(C(a+b)=C(a)+C(b)\)。對於ECC來說,就是\(aG+bG=(a+b)G\),很直白吧。

因此對於輸入=輸出,即\(\sum\limits_i a_i=\sum\limits_j b_j\),其中\(a_i\)為其中一筆輸入,\(b_j\)為其中一筆輸出,它們總和相等。可構造佩德森承諾\(\sum\limits_i C_{i,in}=\sum\limits_j C_{j,out}\),其中每一項\(C\)又該如何構造?如前所說,每個承諾都需要一個盲化因子,以輸入為例,\(C=(x+a)G\),x為盲化因子。

思考為什麼不直接\(C=x+a\),而要乘以G。

又到舉例子時間。假設有一筆輸入,兩筆輸出,如下:

\(C_{in}=xG+aG\Longrightarrow \left\{\begin{aligned}C_{out-1}=y_1G+b_1G\\C_{out-2}=y_2G+b_2G\end{aligned}\right.\)。

顯然,當\(x=y_1+y_2,\ a=b_1+b_2\)時,\(C_{in}=C_{out-1}+C_{out-2}\),這是我們期望的。然而再一細想,可取任意數值n,在\(x-n=y_1+y_2,\ a+n=b_1+b_2\)時,該等式都成立,這就使得交易發起者可以偽造輸出而不被發現(輸出比輸入多了n個幣)。

看來需要有個約束\(a=b_1+b_2\)的方案。研究上式,可以發現n存在是因為各項因子相同(都是G),自然可以這項減那項加,只要加減數值一致,等式便永遠相等。於是我們可改變\(a,b_1,b_2\)的因子,並使其不能與\(x,y_1,y_2\)的因子相互轉換,即不能有\(G_{new}\Longleftrightarrow G\)。於是我們又想到了最初的那個公式,\(G_{new}=rG\),只要r的數值沒有任何人知道,那麼\(G_{new}\)和\(G\)的關係就無人知曉。

第一節裡提到ECC是基於橢圓曲線下的某迴圈子群,因此只要隨機選取該群中任意一點H,它都滿足\(H=rG\)(r不可知)。Confidential Transactions中給了一個選取方法:\(H=to\_point(hash(G))\),hash(G)計算得到的值作為座標x,代入曲線方程計算得到H,即我們需要的\(G_{new}\)。

注意並不是所有hash函式都能恰好得到某個群元素的x座標。

最終輸入=輸出的佩德森承諾為:\(C_{in}-\sum\limits_{i=1}^2C_{out-i}=xG+aH-y_1G-b_1H-y_2G-b_2H=0\)。

monero構造上述承諾稍有不同,留待以後講解。

接著構造金額>0的佩德森承諾,所謂範圍證明(Range Proof)。以輸出\(C=yG+bH\)為例,看如何改造。

  1. 將金額b表示成二進位制形式:\(b=b_02^0+b_12^1+\cdots +b_n2^n,\ b_i\in \{0,1\}\);
  2. 將私鑰y對應拆分為n個隨機數之和:\(y=y_0+y_1+\cdots +y_n\);
  3. 根據加法同態,C可表示為:\(C=C_0+C_1+\cdots +C_n\),其中\(C_i=y_iG+(b_i2^i)H\);
  4. 針對每個私鑰\(y_i\),取兩個公鑰\(\{C_i,C_i-2^iH\}\),兩者必有一個等於\(y_iG\),所以真正的金鑰對為\(<y_i,y_iG>\)。將該公鑰對進行環簽名形成二元環簽名,表明構造者持有這兩個公鑰的其中一個私鑰。有多少個私鑰,就對應多少個二元環簽名;
  5. 礦工只需進行兩步驗證:1、\(C_i\)之和是否等於C;2、各個環簽名是否正確。

範圍證明技術只認無符號正整數,假設採用8bit無符號整數表達金額,若b為負數(-1),那麼在補碼錶示法中,高位全部都是1,b就會被誤認為是255進行處理,這就會導致\(C\ne \sum C_i\),無法通過驗證,這就確保了b的值沒有二意性,只能為正整數。

 


 

參考資料:

https://cryptonote.org/whitepaper.pdf

Ring signature  環簽名(Ring signature)

monero門羅幣ringsignatures環簽名演算法淺解:(2)AdamBack提出的改進演算法

Zero to Monero   Ring Confidential Transactions

 

 

轉載請註明本文出處:https://www.cnblogs.com/newton/p/11376002.html