Merkleization

什麼是 SSZ SimpleSerialize,為什麼要開發它?

  • April 1, 2020

我們正在查看最新的規範,它沒有太多的背景資訊或解釋 SSZ 試圖實現的目標。SSZ的主要目標是什麼?

它的序列化和 Merkleization 優勢的範例將非常有幫助,因為這可能是一個規範問題。

SimpleSerialize (SSZ) 是 Eth2 中使用的規範序列化格式。SSZ規範指導讀者如何執行兩個不同的任務:

  1. 編碼/解碼:如何將 Eth2 資料結構(例如 aBeaconBlock或 a BeaconState)編碼為可以通過網路發送或儲存在數據庫中的字節字元串。
  2. 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 位整數欄位 (idnext) 和一個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.
]

正如我們所見,欄位按照定義的順序進行編碼。“固定大小”項目直接序列化到緩衝區中,而“可變大小”項目首先序列化為指向項目實際開始的“偏移量” 。在附加所有固定大小的項目和偏移量之後,附加可變大小項目的實際(不是偏移量) 。

  1. 默克化

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

為了幫助範例,我們定義了返回andhash_concat(a, b)的連接的 SHA256 雜湊值。例如,。a``b``hash_concat([1], [2]) == hash([1, 2])

首先,我們確定ExampleMerkle 樹的葉子:

# 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) 

引用自:https://ethereum.stackexchange.com/questions/74005