什麼是 SSZ SimpleSerialize,為什麼要開發它?
我們正在查看最新的規範,它沒有太多的背景資訊或解釋 SSZ 試圖實現的目標。SSZ的主要目標是什麼?
它的序列化和 Merkleization 優勢的範例將非常有幫助,因為這可能是一個規範問題。
SimpleSerialize (SSZ) 是 Eth2 中使用的規範序列化格式。SSZ規範指導讀者如何執行兩個不同的任務:
- 編碼/解碼:如何將 Eth2 資料結構(例如 a
BeaconBlock
或 aBeaconState
)編碼為可以通過網路發送或儲存在數據庫中的字節字元串。- Merkleization:如何找到給定資料結構的散列(從技術上講,它是Merkle 根,而不是普通的“散列”)。
對於每一個任務,我都會說明目標(或我對它們的理解)以及每個任務的範例。
1.編碼/解碼
據我所知,從來沒有關於 SSZ 目標的規範聲明。但是,我認為可以公平地說這些是一些目標:
- 簡單:這種格式最初是由 Vitalik Buterin 定義的,他想要一種比 RLP(Eth1 編碼方案)更簡單的格式。
- 雙射:對於某種類型的某些實例,該實例應該只有一個SSZ 表示;您永遠不能讓不同的字節代表相同的
BeaconBlock
. 使用此屬性,SSZ 編碼形成該實例的“身份”。- 緊湊:SSZ 字節需要通過網路發送,因此它們應該是緊湊的(不影響簡單性)。
- Merkle-first:在設計上與將在 Eth2 後期生效的 Merkle-proof 方案兼容。
- 遍歷成本低:SSZ 的第一個版本不包括“偏移量”方案,但是 Peter Szilagyi (Geth) 建議包括偏移量,這將使遍歷序列化結構的欄位變得便宜。這對於可能想要讀取結構的單個欄位而不解碼整個事物的受限環境(例如,EVM)非常有用。
編碼/解碼範例
我將提供一個簡單的例子,同時也向讀者推薦@protolambda 的SSZ 編碼圖。
讓我們假設這個虛構的資料結構,因為 Eth2 規範中的結構都不適用於這個例子:
class Example id: uint64, bytes: List[uint8, 64] next: uint64
有
Example
兩個 64 位整數欄位 (id
和next
) 和一個bytes
可以容納 0-64 字節的欄位。讓我們實例化它:my_example = Example(id=42, bytes=List[0, 1, 2], next=43)
現在,讓我們看看輸出
serialize(my_example)
:# serialize(my_example) # # Note: this is a single byte-array split over four lines for readability. [ 42, 0, 0, 0, # The little-endian encoding of `id`, 42. 12, 0, 0, 0, # The "offset" that indicates where the variable-length value of `bytes` starts (little-endian 12). 43, 0, 0, 0, # The little-endian encoding of `next`, 43. 0, 1, 2 # The value of the `bytes` field. ]
正如我們所見,欄位按照定義的順序進行編碼。“固定大小”項目直接序列化到緩衝區中,而“可變大小”項目首先序列化為指向項目實際值開始的“偏移量” 。在附加所有固定大小的項目和偏移量之後,附加可變大小項目的實際值(不是偏移量) 。
- 默克化
Eth2 不只是使用一個簡單的 SHA256 塊編碼散列來辨識它(例如,
sha256(serialize(block))
。相反, Eth2 中的所有散列都是默克爾根。決定對所有雜湊使用 Merkle-roots,以便輕量級客戶端和執行環境可以使用 Merkle-proofs 來了解 Eth2 狀態的部分內容。例如,如果輕客戶端具有某個塊根的可信 32 字節散列,我可以向該輕客戶端提供一個簡潔的加密證明,證明驗證
n
者的餘額為x
.然而,使用默克爾根作為規范雜湊的決定具有很大的計算成本。Lighthouse 中的同步瓶頸(我工作的客戶端)正在執行這些 Merkle 雜湊。其中一些常式需要幾十毫秒,而一個簡單的常式需要幾
sha256(serialize(block))
微秒(慢幾百萬倍)。所以,我想說 Merkleization 方案的目標是確保受約束的環境(輕客戶端、執行環境、eth1 等)可以訪問輕量級證明,他們可以使用這些證明來做出重要決策。
默克化範例
讓我們使用以前的
Example
類型以及my_example
實例化。我再次建議@protolambda 的圖表:SSZ hash-tree-root and merklization。為了幫助範例,我們定義了返回and
hash_concat(a, b)
的連接的 SHA256 雜湊值。例如,。a``b``hash_concat([1], [2]) == hash([1, 2])
首先,我們確定
Example
Merkle 樹的葉子:# id: little-endian 42, right-0-padded to 32 bytes. leaf_0 = [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # bytes: when a list is hashed, you first hash the list values (right-0-padded to the next multiple of 32) along with the little-endian encoding of the list length (aka., "mixing in the length"). leaf_1 = hash_concat( [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 0, 0, 0] ) # id: little-endian 43, right-0-padded to 32 bytes. leaf_2 = [43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
現在,我們將葉子散列到 Merkle 樹的中間層:
# Hash the concatenation of `id` and `bytes`. node_1 = hash_concat(leaf_0, leaf_1); # Hash the concatentation of `next` and a "zero leaf" (32 zero-bytes), since there is no fourth element. node_2 = hash_concat(leaf_2, [0; 32])
最後,我們可以用 找到這棵 Merkle 樹的根
root = hash_concat(node_1, node_2)
。最終,我們生成了一個嵌套的 Merkle 樹,如下所示:
root | ----------------- / \ node_1 node_2 | | ------ ----------- / \ / \ id bytes_root next ZERO_LEAF | ------- / \ bytes len(bytes)