最近有一個項目 Grin,以及它背後的協議 MimbleWimble 在中國異常火爆。事實上,Nervos & Cryptape 研究員、魯汶大學 COSIC 實驗室在讀博士生張韌在兩年之前,就給 Terry 和 Jan 推薦過 MimbleWimble 協議,而這次 Fork It 我們有幸請到了張韌來和我們聊聊 MimbleWimble。

以下文字稿整理自 Fork It #4,爲了保證閱讀的完整性,我們以張韌爲第一視角,對部分內容進行了整理,並對節目中的技術細節進行了補充(如和播客內容有出入,以本文內容爲準)。另外,完整的播客節目中的各種小細節也十分有趣,歡迎複製此鏈接收聽:http://forkit.fm/4

原文標題:《張韌全面解析 MimbleWimble》

初識 MimbleWimble

大概是兩年前,我發現了 MimbleWimble 協議 ,當時就覺得它特別好玩,神祕感十足。

這個協議是以匿名的方式發佈在 Bitcoin 的 IRC Channel 裏,化名作者的名稱取的是伏地魔的名字,而且是法文版《哈利波特》中伏地魔的名字,叫 Tom Elvis Jedusor。作者在 Tor 網絡裏放了一個 Dot Onion 的 Link,這個 Link 指向一個 TXT 文件,就是 MimbleWimble 協議。作者通過這樣的方式更好地隱藏了自己的身份,從此之後便消失匿跡了。 不過他放的協議並沒有寫完,後來經過 Blockstream 的 Andrew Poelstra 深入的研究,證明了 MimbleWimble 系統的安全性,纔算把這個協議真正的完成。所以,這個協議的提出者本身可以佔 idea 的功勞,而 Andrew Poelstra 要佔一半的功勞。

從密碼學家的角度看,在區塊鏈上把所有的交易金額用明文存放,是一個非常沒道理的事。而且把所有已經花掉的交易永久的存在硬盤上,才能保證協議本身的安全性,也很浪費。

而 MimbleWimble 巧妙地組合了一些之前的設計,把這兩個問題解決了,給人的感覺非常優雅。

定義 MimbleWimble

要理解 MimbleWimble 首先要搞清楚它的定位,它的定位就是 Privacy Coin,一個可以很好的支持隱私的密碼學貨幣。我覺得在 MimbleWimble 之前只有兩個 Privacy Coin,一個叫 Zcash,一個叫 Monero。而別的號稱自己是 Privacy Coin 的,技術都不夠強。

但是 Zcash 和 Monero 具有同樣的問題:它們爲了自己的隱私,都需要維護一個集合,這個集合裏存着所有已經被花掉的交易輸出,我們可以把它理解爲「作廢列表」。

如果要驗證一個新的交易是不是有效交易,首先需要檢查這個新交易的交易輸入是不是在「作廢列表」裏面,如果這個交易輸入已經在「作廢列表」裏面了,就證明這個交易輸入已經失效,而如果沒有在「作廢列表」裏面,就證明這個交易輸入是有效的,交易會被礦工承認,緊接着礦工會把這個交易輸入放到「作廢列表」當中。

所以對於 Zcash 和 Monero 來說,它們都面臨着一個很嚴重的問題——交易輸出是永遠增長的。一個公網全節點想要驗證一個新交易是否有效,必須儲存完整的交易數據集合。隨着交易筆數的累加,數據量會越來越大,它使用起來會變得非常不友好。

MimbleWimble 協議就沒有這方面的問題,它相當於比 Zcash 和 Monero 有更好的 Scalability,可以應付狀態增長的問題。

舉個例子,應用了 MimbleWimble 協議的 Grin Coin,它的硬盤空間佔用非常小,它可以極大的壓縮已花費的交易輸出所佔的硬盤空間。

如果只是簡單的把已經花掉的交易輸出刪除,那麼在驗證區塊有效性時,就無法驗證這個區塊是否有效,也無法驗證交易簽名。但是在 MimbleWimble 協議裏是不存在這個問題的。

對於 MimbleWimble 來說,把已花費的交易輸出和對應的交易輸入都刪掉以後,完全不影響整個交易圖的驗證,所有剩下的簽名依然是有效的。而且在刪掉已經花掉的交易輸出之後,整個區塊鏈依然可以通過簽名和工作量進行驗證。

大家可以簡單的理解爲這是一種類似 Accumulator 的東西,它可以把過去很多的密碼學 Proof 壓縮成一個很小的證據,有點像 Merkle Tree,但是會比 Merkle Tree 更加高效,性能更強。

對 MimbleWimble 的 3 個誤解

誤解 1:壓縮空間 = 更好的隱私?

很多人把 MimbleWimble 壓縮硬盤存儲空間這個特點當做更好的隱私來賣,但事實上它並不能實現更好的隱私。如果你選擇把自己的硬盤空間壓縮,而另外一些人選擇不壓縮,那麼他們依然可以得到完整的交易圖,可以從裏面挖出交易的交易關聯。

當然,在刪除硬盤存儲空間的時候,還是需要保留最新幾天的區塊原始數據,因爲不知道會不會有分叉、雙花攻擊等這些情況出現。當確定鏈已經足夠長,數據不會被改變時,才能把那些已經確定不會被改變的硬盤空間壓縮。

網絡中的全節點一般是默認裁剪存儲空間的,但是也可以對源代碼進行修改,讓它不裁剪。

誤解 2:單個交易方可以否認某筆交易?

MimbleWimble 協議有一個特性,它的交易構造方式是互動式的,這意味着一筆交易必須要雙方參與才能構造完成,這和比特幣、以太坊非常不一樣。

拿實現了 MimbleWimble 協議的 Grin 舉例子,如果我給交易所一筆錢,而交易所不做任何事情,它是收不到這筆錢的。只有當它參與了跟我互動的協議,完整的構造出這筆合法的交易以後,它才能收到這筆錢。

如果它跟我構造了這筆合法的交易,也就證明了它知道這筆交易的存在,所以說不存在交易所給我錢,我可以說沒有,這個同樣是外界對 MimbleWimble 的一個誤會。

誤解 3:交易需要交易雙方同時在線?

這個互動式交易並不要求交易雙方都在線,交易雙方完全可以採取發郵件的方式來完成交易構造。

前段時間我去看了 Grin 的錢包演示,錢包把交易構造過程中的中間數據,都存成一個文件。對於輸入的人來說,他可以生成一個文件,然後把這個文件發給交易的接收者。交易接收者收到這個文件以後,可以把文件拖到自己的錢包客戶端裏,在客戶端裏構造完成整個交易,再把它作爲一個文件發回給交易的發出者,完成交易的構造。所以需要雙方參與,但並不代表需要雙方同時在線,可以是異步的過程。

MimbleWimble 技術詳解

現在我來和大家詳細的說說 MimbleWimble 的技術細節。

MimbleWimble 有三個基本組件,第一個基本組件叫 CT (Confidential Transaction),第二個基本組件叫 Coin Join,第三個組件叫 OWAS (One Way Aggregate Signatures)。

剛纔我們講的是 OWAS 中的一部分。現在我從 CT 開始簡單的給大家捋一下。CT 最複雜,剩下兩個都比較簡單。

Confidential Transaction

CT 這個 idea 最早由 Blockstream 的 CEO Adam Back 提出。Adam Back 被稱爲比特幣的教父,因爲他是挖礦算法 Hashcash 的發明人,他是被中本聰在論文裏引用了的人。

對於一個礦工或者公網節點來說,他們最關心的是某個交易是否是有效交易,以及交易並沒有造成系統的通貨膨脹(沒有創造出新錢,比如發出者花出 50 個比特幣,接收者接收到的是 50 個而不是 51 個)。除此之外,他們對於交易的具體金額並不關心。

所以 Confidential Transaction 最初的想法就是,是否能把交易金額變成密文,同時保證交易的有效性不受影響。

Gregory Maxwell 是 Blockstream 的另一個 Co-founder,也是比特幣最早的五個核心開發者之一,他真正設計了一個這樣的協議,以一種方法把交易的金額加密,但完全不影響交易的有效性。

協議把每筆交易的輸入或者輸出可表達爲以下形式:(也叫 Pedersen Commitment)

v*G + r*H

(其中 v 爲金額,r 是一個隨機數,代表盲化因子,G 爲生成元 1,H 爲生成元 2)

這個也叫對交易金額進行盲化操作,交易的「金額」很好理解。生成元 1、生成元 2 是橢圓曲線密碼的兩個生成元,一個數值在乘以生成元之前是明文,乘以生成元之後,大家可以把它理解爲一個經過橢圓曲線密碼簡單加密過後的密文。這項運算是單項的,雖然大家都知道生成元是多少,但是一旦乘過之後就不能再把它倒推回去,也沒有人知道原來的明文是多少,這個難題叫做「離散對數問題」。

盲化因子是交易輸入或輸出的構造者自己選的隨機數,這個隨機數只有自己知道,不能告訴別人。

對每一個交易金額進行盲化操作以後的好處就是,它可以保持「加法同態性」,公式如下:

(v1G + r2H)+(v2G + r2H)=(v1 + v2) G+(r1 + r2)H

(其中 v1、v2 爲交易金額 1 和交易金額 2,r1 和 r2 爲盲化因子 1 和盲化因子 2,G 爲生成元 1、H 爲生成元 2)

簡單來說就是,對加密後的數字進行加法操作,和對加密之前的數字進行加法操作再把它加密,是等價的。

到目前爲止最重要的就是理解什麼叫加法同態,就是先做加法後加密,和先加密後做加法,兩個不同的運算順序得到的結果是一樣的。一個不知道具體金額的人,依然可以驗證交易的有效性,可以驗證兩個金額加起來是對的。

當我們在交易中需要添加交易費用的時候,它是以明文的形式交給礦工的,並不需要額外對交易費用進行復雜的加解密操作。礦工將 f*G 與交易輸出的 Pedersen Commitment 相加,驗證是否能夠配平交易輸入的 Pedersen Commitment (f 表示交易費用的金額)。

知識點:Pedersen Commitment:將明文表示的未花費交易輸出(UTXO)數值替換爲加密
Commitment,即,在不泄露交易價值的情況下,使得人們有可能驗證交易的有效性。

簡單總結一下加法同態的兩個好處,一個是無需解密,不知道具體交易金額也可以驗證交易有效性,另外一個是可以直接和明文形式的交易費用進行運算,礦工可以直接看到自己的收入是多少,而且可以驗證這個等式是有效的。

還有一點是,交易的發出者和交易的接收者,他們彼此都不知道對方的盲化因子,即交易的輸入者不知道輸出者的盲化因子,而交易的輸出者也不知道輸入者的盲化因子,但他們知道交易金額。

這會導致的結果是,等式兩邊交易金額那一項可以被配平抵消掉,但盲化因子這一項無法抵消。如果你把輸出減去輸入,最後會有一項:

(ro-ri)*H

(ro 爲交易輸出的盲化因子之和,ri 爲交易輸入的盲化因子之和,H 爲生成元 2)

我們可以把它起名叫做「餘項」。

注:(ro-ri)*H 或者 (ri-ro)*H 等於餘項並不重要,取決於項目具體的選擇。

在 MimbleWimble 的 Confidential Transaction 裏,爲了證明這個交易不是胡亂構造的(交易輸出者和輸入者,的確都知道自己的盲化因子,並沒有拿別人的交易輸出來構造自己的交易),需要有一種方式來解決。

在比特幣裏面比較簡單,就是直接拿自己的私鑰對整個交易做簽名,證明這個交易是你參與並構造的。

但 MimbleWimble 方式很新穎——你只需證明你知道盲化因子差即可。

(注:此處的盲化因子差爲「交易輸出盲化之和—交易輸入的盲化因子之和」,下文同樣爲此概念)

因爲輸入者和輸出者只知道自己的而不知道對方的盲化因子,他們之間需要一個協議來共同證明,兩個人的知識加在一起,可以算出盲化因子差。這和直接對整個交易進行簽名是等價的,可以證明我沒有胡亂構造交易。

換一種方法說,如果我能證明我知道盲化因子差,也就同時完成了對交易的授權簽名,而不需要用輸入的私鑰對交易進行簽名。

在 MimbleWimble 裏,事實上是用盲化因子差對一個固定的字符串,比如說 0,做簽名,證明盲化因子差我知道,然後我同時公佈餘項。

以上這段的核心是,對盲化因子差值的知識,可以替代比特幣傳統交易裏的私鑰。

還有額外的一點,我們需要有一個 Range Proof。

對金額的部分,你需要證明這個金額部分沒有負數,因爲我們不希望有人構造一個交易,交易輸入是 1 交易輸出是 2 和 -1,這會憑空製造出一些錢。

爲了證明這個交易所有的輸出裏都沒有負數,每個交易輸出需要有一個 Range Proof,一個簡短的零知識證明,來證明所有的交易輸出都小於某個閾值,且都是正的。

給大家一個直觀的概念,每個交易輸出加密後的密文大概是 33 個 Bytes,但是 Range Proof 現在是 5 KB 左右,而且這已經是經過優化的結果了,因爲 Range Proof 相當於是一個零知識證明,它很難做到更短。

Gregory Maxwell 做了第一版的 Confidential Transaction,他完善了 Range Proof,之後斯坦福大學的學生 BenediktBünz 提出了一個改進版的 Range Proof ,比原來 Gregory 的 Range Proof 長度更短,驗證速度快。改進版的 =Range Proof 叫做 Bulletproofs,發表在信息安全領域 Top 1 的頂會里。Bulletproofs 讓 RangeProof 縮小很多,大小約 700 Bytes。

Pieter Wuille、Gregory Maxwell 都是這篇文章的作者,他們幫助 Benedikt 完成了 Bulletproofs 的實現和測試,這樣也意味着最新的密碼學進展直接應用到了 MimbleWimble 的 Confidential Transaction 構造裏。

我們現在簡單回顧一下第一部分,MimbleWimble 有三個基本組件,第一個是 Confidential Transaction,它實現了對金額的加密,直接利用盲化因子差爲私鑰簽名來證明自己對餘項的知識,替代了傳統的直接用輸入的私鑰做簽名,而且在 Confidential Transaction 中還要附帶一個 Range Proof。

Coin Join

現在我們開始講 MimbleWimble 的第二個組件 Coin Join。

Coin Join 的想法特別簡單,當我有兩個交易時,每一個交易等式左邊和右邊都是可以配平的:把兩個交易等式的左邊加在一起,右邊加在一起,還是一個合法的交易。比如說一個交易有輸入 1 和輸出 1,另一個交易有輸入 2 和輸出 2。我們構造一個新的交易,這個交易有兩個輸入,輸入 1 和輸入 2,有兩個輸出,是輸出 1 和輸出 2,它也是一個合法的交易。

Coin Join 最早是由 Gregory Maxwell 提出的,這是比特幣早期能夠實現更好隱私的方案。

但是 Gregory Maxwell 提出 Coin Join 協議時,大家有一個很大的疑問,就是交易的每個輸入和每個輸出只有一種配平方法。比如說一個交易的輸入是 8 BTC,輸出是 3 和 5,另一個交易輸入是 20BTC,輸出是 10 BTC 和 10 BTC,我們把這兩個交易的輸入放在一起,輸出放在一起,人們依然可以看出來,哪個輸出最早是對應哪個輸入,所以 Coin Join 本身並沒有提供多麼好的隱私。

但是當它和 Confidential Transaction 結合在一起,把所有的交易輸入和輸出都加密了以後,就能夠提供非常好的隱私。

Coin Join 協議很簡單,但是直接把它和 CT 連在一起會有一個問題,如果用傳統的簽名方式(每個交易的合法性,用交易輸入的公鑰所對應的私鑰做簽名),我們完全可以根據簽名所用的地址,來判斷哪個輸入對應的是哪個輸出。

所以 Coin Join 加 Confidential Transaction 就自然地被要求選擇另一種簽名方式,就是直接用餘項做公鑰,利用盲化因子的差做私鑰,用這個公私鑰對對 0 做簽名。這就解決了可以直接通過交易的簽名,把輸入和輸出聯繫在一起的情況。

Zcash 將來是有可能實現零知識證明的智能合約的,只不過會非常的慢。在 Cash 場景下,比特幣對智能合約的支持我個人覺得是足夠的,因爲比特幣有一些基本的驗證功能,我們把這些功能排列組合,可以完成現實世界非常多智能合約需要的驗證任務。

Grin Coin 在設計時的智能合約支持能力,比比特幣和 Zcash 都要差。但是這也激勵了研究者去思考,在這個沒有地址標識,很多交易會被刪掉的系統裏,怎麼夠實現智能合約。

Coin Join 還有一個問題,之前我提到對 0 進行簽名,如果真的是對 0 做簽名,就可以把兩個簽名直接加在一起,做一個新的合法簽名。但是在協議的實際操作過程中,發現用 0 作爲簽名並不好,而應該對固定格式的字段做簽名。

因爲每個交易簽名的對象不一樣,所以並不能直接把這幾個簽名加在一起。那怎麼樣能夠實現既可以把交易用 Coin Join 連在一起形成一個巨大的交易,又不影響交易驗證?連在一起以後,我們有沒有辦法重構出哪個交易輸入對應的是哪個交易輸出?

One Way Aggregate Signatures

這就涉及到他的第三項技術,叫 One Way Aggregate Signatures, 單向的聚合簽名。

單項聚合簽名把盲化因子之差 x 拆成兩項 x1 和 x2 ,其中 x1*H 作爲公鑰公開,當然 x1 本身不公開,需要用簽名來證明我知道 x1。 x2 直接公開,證明我也知道 x2 。 x1*H + x2*H 等於之前的餘項。

現在將盲化因子之差拆成兩項後,x1*H 叫做 Kernel Excess,x2 叫做 Kernel Offsets,Kernel Offsets 就是偏移量。

把一個盲化因子之差拆成兩部分有什麼好處呢?

當一個區塊裏所有的交易,組成一個大的 Coin Join 交易時,可以把那些作爲明文公開的 Kernel Offsets,直接加在一起,這樣就扔掉了每一筆交易的信息。

而對於那些需要簽名的部分,因爲簽名的部分並不是都對 0 簽名,而是對一個固定的字段簽名,他們不可以聚合在一起,這些簽名需要單獨存儲。也就是說把 Kernel Offsets 全都加在一起,一個區塊只存一個 Kernel Offsets,但是 Kernel Excess 還是分別存。每筆交易的 Kernel 中都存儲了一個 Kernel Excess 以及使用 x1 對某個固定字段的簽名。

這樣做的好處是,當拿到一個巨大的 Coin Join 交易區塊時,可以重新把 Kernel Offsets 的聚合項和所有簽名合在一起,來驗證這個區塊裏所有交易都是有效的。而因爲 Kernel Offsets 已經合在一起了,就沒有辦法把加在一起的 Offsets 再拆開。我們沒有辦法知道,每個簽名對應的是哪個輸入和哪個輸出,所以就實現了 One Way Aggregate Signatures。

我再解釋一下,One Way Aggregate Signatures 根本的作用是,當你拿到一個區塊中的很多交易輸入、輸出時,你沒有辦法判斷哪些交易的輸入和輸出原本是一個交易。它的做法是把每個交易的盲化因子之差分成兩項,一項用簽名證明我知道,另一項直接公佈明文證明我知道。而對直接公佈明文的那一項,對於所有的交易,可以直接把它們加在一起。

這裏我再補充一下 Cut Through 的概念。

因爲所有已經花掉的交易輸出,在區塊裏等式的左邊出現了一次,右邊又出現了一次,所以我們可以把等式兩邊都有的項都劃掉。Cut Through 的概念就是,可以把已經花掉的交易輸出在區塊裏刪掉,當有了很多的區塊以後,我們可以跨區塊的把不同交易的交易輸入和輸出刪掉,從而實現壓縮硬盤存儲空間的目的。

但是 Mimblewimble 會有幾個很難聚合的點,一個是所有交易輸出的 Range Proof, 隨着時間的推進,Mimblewimble 系統裏的 UTXO 會越來越多,而每一個交易輸出,都有一個自己的 Range Proof,來證明我這個輸出是非負的。

(注:UTXO 並不會隨着時間的推移單調遞增,這裏表達的是 UTXO 隨着時間推進,用戶數量變多,UTXO 變多)

當你需要去驗證 UTXO 的有效性時,需要把這些 Range Proof 從頭到尾算一遍,相當於驗證單個交易輸出有效性的時間和存儲空間都要比比特幣多一些。

所以 Mimblewimble 計算量和存儲空間的瓶頸都是 Range Proof。另外,盲化因子被拆成兩項後,每一個新的交易都有自己的簽名,這個不可聚合項也是無法壓縮的。

當你遍歷整個區塊鏈時,它也會影響驗證的時間,以上也是最影響 Scaling 的兩項。

MimbleWimble 的具體實現——Grin

接下來我們來說一說 MimbleWimble 的具體實現—Grin,它的挖礦算法叫做 Cuckoo Cycle。

Cuckoo Cycle 簡單說就是兩個很長的哈希表,每個表都有很多節點,以一種僞隨機的方式在這兩個哈希表當中連上很多根線。

哈希表 1 只能和哈希表 2 連接,哈希表 2 只能和哈希表 1 連接,表 1 的元素之間不能連接,表 2 的元素之間也不能連接。

Puzzle 本身是要找到一個長度正好爲 42 的圈,就是表 1 的 A 節點連到表 2 的 B 節點,表 2 的 B 節點連到表 1 的 C 節點,這樣連來連去,找出一個長度爲 42 的。

這個設計最初的目的是讓這個 Puzzle 本身佔用儘量多的內存空間後才能開始解。

因爲想要知道跟某個表 2 節點連接的有哪些表 1 的點,最好的辦法就是先把所有的邊都算下來,以某種數據結構保存,接着在這個數據結構裏搜索,這樣就能保證搜索之前需要先開一塊很大的內存空間,以此來避免計算 Puzzle 只和 CPU 的使用有聯繫,而大的內存空間最初的初衷也是給 ASIC 造成一定的困難。

但是隨着時間的發展,大家意識到 ASIC 無論如何都會存在,所以之後根據當時解這個問題最好的算法,做了一些改進,使得 Puzzle 用 ASIC 也可以算。它會在協議的初期選擇一個對 ASIC 不太友好的協議,但是兩年以後會全部替換成改進版的對 ASIC 很方便的協議。

這裏再補充一點,作爲一個密碼學家的學生(我不是密碼學家,但是我老闆是),Cuckoo Cycle 讓我覺得最震撼的一點是,在設計之初,它只有直覺,並沒有數學證明哪個算法一定是最快的。而這些解 Cuckoo Cycle 的算法,都是在 Cuckoo Cycle 已經很有名的時候,大家一起討論出來的。

所以在沒有數學證明時,完全可能出現一種情況,就是突然有一個人發明了一個解 Cuckoo Cycle 特別的快算法,以至於後來所有的 Grin Coin 都讓他一個人挖了。這對 Grin 目前來說也是一個問題

這對我來說也是一個教訓。在 13 年時,我花了一年的時間試圖設計一個自己的 PoW 算法,它和 Cuckoo Cycle 非常接近,但當時折磨我的一點是,我找不到一個數學證明,證明某一個算法解 Puzzle 一定是最快的,所以最後那篇文章我一直都沒發。現在想起來覺得很遺憾,因爲這種不成熟的 idea 完全可以扔出去讓大家討論,說不定能討論出一些很好的 idea,不是所有的東西都需要有數學證明的。

所以當我前段時間聽到 Cuckoo Cycle 設計時,心裏是感慨萬千。

到現在爲止,有關 MimbleWimble 的細節就講完了。