傳統分片技術存在雙方需要知道對方歷史區塊的包袱,DFINITY 採用 BLS 文件簽名算法或可解決這個問題。

原文標題:《DFINITY | Paul Liu:一條沒有歷史包袱的鏈》
撰文:Paul Liu,DFINITY 核心工程師
整理:BeWater

2021 年 9 月 4 日,在 BeWater DevCon 2021 全球開發者大會上,DFINITY 核心工程師 Paul Liu,曾任英特爾實驗室研究科學家,爲 X86 架構構建了 Haskell 編譯器,他就密碼學如何改變共識這一主題,進行了分享。

傳統分片技術存在分片雙方需要知道對方歷史區塊的包袱。DFINITY 採用了 BLS 文件簽名,引入對 DKG 的算法進行一些改進,加入證明,解決最長鏈驗證問題 ,子網之間的交換便無需歷史包袱,區塊鏈變成了一臺「互聯網計算機」,甚至可以在上面跑網站。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

Hello,大家好,我是 DFINITY 的工程師 Paul,很感謝大會的邀請,在這裏能有機會和大家分享一下技術心得。我今天帶來的題目主要是圍繞着怎麼用一些加密技術來改進共識協議,能夠爲共識協議去掉一些歷史包袱。

比特幣誕生以來提出了一個口號:「Don\’t trust. Verify」。就是說,「不要去相信,要去驗證」。但具體怎麼驗證呢,裏面的細節其實還比較多。

歷史區塊包袱

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

我們這裏看一個例子,後面的區塊需要對前一個區塊進行哈希,還包括區塊本身的內容,比如用戶的交易這些。

假如給你一個區塊要驗證它的正確性,這個怎麼做呢?通常,比特幣的做法需要知道這個區塊是屬於最長鏈,經過多輪區塊以後我們基本上可以確信這個區塊不能夠再更改了,所以它是安全的。

要知道這個區塊屬於最長鏈,它的從屬關係,實際上是要知道這個區塊它之前的節點是正確的,需要一直回溯到創世區塊,我們假設認定創世區塊是正確的,這樣算出來的最長鏈。最長實際上也是難度的概括,我就不細講了。

算出來最長鏈才能驗證這個區塊鏈,這勢必就牽扯到你需要知道所有的歷史區塊,這樣的負擔對於用戶來說基本上是不可接受的。對於節點來說,可能也就是第一次進行同步的時候,一個新節點加入進來進行同步的時候,它可能需要知道所有歷史記錄,這才能驗證之後區塊的正確性。

怎麼樣解決這個問題,因爲這對於容量的擴充是一個挑戰,始終是需要保持所有的歷史區塊的記錄。

傳統分片技術

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

擴容方面我們通常的方案是做分片,我們把整個區塊鏈的狀態分成好多個片,然後每一個片實際上通過自己的區塊鏈在運行。如果一個交易需要跨片的話,從一個區塊鏈發起的交易到另外一個區塊鏈上去接收,發起方和接收方勢必要驗證對方區塊鏈上面的區塊,每個分片就需要知道所有其它分片過去的歷史區塊,這樣相對於單鏈來說其實負擔更大了。可能有一些刪除優化算法,但這些優化算法實際上也就是提高了一些效率,並沒有達到質的飛躍。

我們看看以太坊 2.0 是怎麼解決分片的問題?

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

它採取分層的結構,在最上面一層有一個叫 Beacon Chain,它是對其它的 Chain 做一個快照,其它的 Chain 只需要知道 Beacon Chain 是經過確認的,然後它就可以驗證另外的 Chain。這個實際上還是隱含着一個條件,你要能夠驗證 Beacon Chain,就要知道 Beacon Chain 所有的歷史區塊。雖然對不分層的結構而言它有一定的進步,但實際上跟單鏈來說它也沒有特別大的差異。所需要的難度,就是保持歷史區塊所需要的挑戰基本上是一樣的。

所以,我們可以看到歷史區塊實際上是有包袱的。有沒有一種辦法能夠把這個包袱完全拋棄掉,這是今天所要探討的一個問題。

BLS 門限簽名

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

我們考慮一個簡單的例子,跟剛纔我們提到的 Beacon Chain 目的有點相關,它主要是作爲隨機數的序列,公衆能夠驗證的公平的隨機數。它必須滿足幾個條件:

  • 不可以被預測:如果能夠被預測的話,實際上沒有安全性可言了。
  • 公平:公平性是說參與計算的節點,它不可以影響下一個隨機數數值的公平性,它可以影響的話實際上帶來了安全性上的問題。
  • 公共可驗證:即前面兩點要能夠被公衆所驗證。

所以要滿足這三點才能夠做一個安全的隨機數的序列區塊。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

有一個算法叫做「門限簽名」,它的基本原理如上圖所示。

我們可以看到這個例子裏面有四個節點,每個節點有各自的私鑰,然後每個節點單獨對數據進行簽名,對同一個數據進行簽名。如果我們能夠收集到三個簽名,將這三個簽名合在一起,就能夠合成爲一個簽名,叫做「集體簽名」。集體簽名有一個有意思的地方,無論是哪三個你收集起來的單獨簽名,你把它們合成起來合成一個集體,都可以成爲一個集體簽名。

這個集體簽名可以被驗證,驗證集體簽名是通過一個公鑰,這個是集體公鑰,而不是某一個節點的公鑰。集體公鑰是最初開始生成的時候就確定的,所以這個集體公鑰可以拿來驗證集體簽名的消息。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

我們怎麼樣能夠信任集體簽名呢?

首先,我們必須要能夠信任最開始的生成過程,假定這個過程是可以信任的,稍後還會重新看內部的具體過程怎麼實現。假如說,生成的節點私鑰公鑰和集體的公鑰都是可以被信任的,除了我們這個節點能參加運算,它還需要知道其它的節點,所以我們需要有些公共信息,就是包括節點的 ID,包括它們的公鑰。

公共信息放在創世區塊裏面,同時對創世區塊進行哈希,來到下一個區塊,我們再對下一個區塊進行哈希,並且做一輪門限簽名,就是閾值簽名,我們收集到足夠多的簽名,把它們合在一起,得到一個集體簽名,這樣就得到下一個區塊。如此反覆,我們就可以把區塊鏈進行下去。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

但是,造成的結果就是,我們不光有隨機數序列的 Chain,還有 Block Chain,這個 Block Chain 記錄了 DKG 所需要的一些消息,以及 DKG 運算的一些結果。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

我們假如說在一定的區間運行一整套 DKG,然後新加入的節點在這時候可以報名之類的,運行完出來一個結果,這個結果包括了下一個區間所需要的節點列表,它們各自的公鑰,還有集體公鑰,這個結果我們把它交給 Summary Block。

實際上我們可以看到 Summary Block 會經過一個門限簽名,這個簽名所需要用的公鑰是上一個區間用的公鑰。我們這個區間 Summary Block 會有之後區間運行所需要的公鑰,意味着運行一次 DKG 加入了新節點,這個公鑰可能就發生了改變。私鑰顯然是發生了改變,因爲有新的節點加入,公鑰也發生了改變,我們就說下一個區間是用新的公鑰來進行驗證簽名的。

如此反覆我們就得到了每一個區間的 DKG,每一個都有 Summary Block。需要驗證 Summary Block,我們必然需要知道上一個區間的公鑰是正確的,我們就需要驗證上一個區間的 Summary Block,這樣它們之間形成了一個鏈條,這個鏈條也是追溯到最開始的創世區塊,所以我們並沒有解決我們想要解決的問題,雖然說做了一些強化,但是沒有從根本性上解決,我們還是需要保持歷史節點,至少是 Summary Block 的歷史節點。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

這個怎麼辦呢?通過研究,我們發現有一種新的算法能夠在運行 DKG 的時候不改變它集體的公鑰。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

這個算法大概的意思:假如說我們這邊有一個節點發生故障導致它丟失了,我們加入一個新的節點,已有的這三個節點還是保留它各自的密鑰,有它各自的祕密,可以把這些祕密通過零知識證明,不暴露它的祕密。可以做一些 reshare,可以分成四份,這四份再組合在一起。這三個節點都把自己的祕密分四份,四份重新組合一下,又得到了四個節點的私鑰和節點的公鑰,它們就可以用節點的私鑰來做簽名。

把這些簽名合在一起,需要驗證集體簽名所需要的集體公鑰,它跟以前是一模一樣的,也就是說集體公鑰是沒有發生變化的。雖然我們又運行了一次 DKG,雖然我們加入了新的節點,但是我們保證了公鑰的不變性。這就解決了剛纔談到的問題,即 Summary Block 需要保持它的歷史區塊的問題。即只需要一個公鑰,就不需要保存歷史記錄了。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

具體到實現上還是有一些新的問題。從節點的角度來說,新節點的加入,它要產生下一個區塊的話,就需要知道前一個區塊的哈希,所以就需要和其他節點交互,得到前一個區塊,比如在 203 的區塊高度,我們發現做今後的計算我們不需要高度 200 以前的數據,我們只需要高度 200 以後的數據,假設這中間數據的依賴關係已經可以做到這點,就是說 200 以前我們可以完全丟掉,這時候在 200 這個高度我們就可以對 200 高度的這些區塊,這些狀態和它一起打個包,把這個包做一個簽名,得到了集體簽名之後,這個包就可以用來給新加入的節點。

它只需要驗證一下這個包是可信的,再跟其它的節點同步一下,我們把從這個包到目前最新的區塊重新同步到新的節點上來,這個新的節點就可以加入接下來的計算了。

通過這樣一個方式,我們能夠對 DKG 的算法進行一些改進,加入零知識證明和 resharing 的方法,我們就可以丟棄歷史包袱。

我們來看一下這個技術在 Internet Computer Blockchain 架構裏面的具體應用。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

首先我們講公鑰。我們只需要一個公鑰,這是 48 個字節,就可以驗證所有的交易。對照一下以太坊,以太坊 Open Ethereum 的客戶端要下載 400 GB 的數據才能夠開始進行計算,這完全不是一個維度的事情了。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

Internet Computer 的節點我們也是採取了分片的做法,這裏爲了區分一下我們把它叫做子網,也就是我們沒有母網,每個子網相互之間可以交換信息,但是它不需要知道對方子網的歷史區塊。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

這裏面有一個子網有一點特殊,我們把它叫做 NNS,它主要的功能就是創建新的子網。它可以爲另外的子網運行 DKG,它通過運行 DKG 產生一個新子網所需要的公鑰。綠色的公鑰是綠色子網所需要的,它最初創世紀的區塊是在 NNS 這個子網上面進行計算得到,所以 NNS 就可以創建其它子網所需要的公鑰,它在這個基礎上再做一個證書,這個證書就是證明子網公鑰的正確性。

我們在跟用戶進行交互的時候,返回的信息,然後用戶需要驗證,它就只需要知道 NNS 的公鑰,加上子網的證書,加上子網的簽名,一整套驗證就可以驗證信息的安全性和正確性。這個驗證是非常簡單的,我們在瀏覽器裏面通過 JavaScript 就可以做到。所以用戶這邊是完全沒有新的要求,只需要通過瀏覽器。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

在節點方面,每個子網節點參與運算,有時候它會發生故障,有新的節點,或者我們知道有的節點被攻破了,我們需要用新機器來替換它,這些過程都是由子網自己完成。只有子網的創世區塊是由 NNS 完成,子網本身運行自己的 DKG。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

我們剛纔提到了它的作用,一方面是替換節點,甚至當子網有非常嚴重破壞的時候,比如說丟失了大部分的節點。

這時候我們怎麼辦呢?通常區塊鏈就沒有辦法修復,但是我們可以通過 NNS 再運行一次 DKG,然後加入新的節點進來,然後通過 Catch-up packages,從之前停止的狀態繼續進行下去,雖然這時候因爲 NNS 運行 DKG 得到的子網公鑰發生了變化,但是這個不影響跟用戶之間的交互,因爲子網的簽名最終是通過它的證書來保證正確性,只要證書被傳到了用戶,用戶就可以通過 NNS 的公鑰來驗證。我們還可以做一些升級它的協議這樣一些管理,自動化的管理應用過程。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

所以拋開歷史包袱之後我們發現是一個新世界,很多之前做不到的事情現在都變得非常簡單,只需要一個公鑰來驗證交易,我們就可以直接在區塊鏈上跑網站,這個網站是直接從區塊鏈上,把網頁送到用戶側,因爲驗證數據的正確性只需要一個公鑰。

然後我們還可以很容易的擴容,擴容我們只需要添加新的子網,就可以處理新的交易,處理更多的用戶,保存更多的狀態,因爲子網跟子網之間是沒有保存歷史記錄的這樣一個負擔的,我們可以修復子網,可以對協議進行升級,這些事情都變得非常簡易。

DFINITY 核心開發者:如何構建沒有歷史包袱的區塊鏈?

今天我的分享就到這裏,想要知道更多的技術細節,歡迎大家到這個網址看一下,因爲這個網址上面提供了我們團隊對一些技術細節講解的視頻,還是非常有幫助。

如果對今天這個分享有什麼疑問的話歡迎聯繫我,我的 github 的賬號和新浪微博的賬號都發在這裏,謝謝大家今天的參與,我們下一次再見。