Simple serialize
Letzte Aktualisierung der Seite: 1. Februar 2026
Simple Serialize (SSZ) ist die Serialisierungsmethode, die auf der Beacon Chain verwendet wird. Sie ersetzt die RLP-Serialisierung, die auf der Ausführungsebene verwendet wird, überall auf der Konsensebene, mit Ausnahme des Peer-Discovery-Protokolls. Um mehr über die RLP-Serialisierung zu erfahren, siehe Recursive-Length Prefix (RLP). SSZ ist so konzipiert, dass es deterministisch ist und sich effizient merkelisieren lässt. Man kann sich SSZ als aus zwei Komponenten bestehend vorstellen: einem Serialisierungsschema und einem Merkelisierungsschema, das so konzipiert ist, dass es effizient mit der serialisierten Datenstruktur arbeitet.
Wie funktioniert SSZ?
Serialisierung
SSZ ist ein Serialisierungsschema, das nicht selbsterklärend ist – vielmehr stützt es sich auf ein Schema, das im Voraus bekannt sein muss. Das Ziel der SSZ-Serialisierung ist es, Objekte beliebiger Komplexität als Byte-Strings darzustellen. Dies ist ein sehr einfacher Prozess für „Basistypen“. Das Element wird einfach in hexadezimale Bytes umgewandelt. Zu den Basistypen gehören:
- vorzeichenlose Ganzzahlen (unsigned integers)
- boolesche Werte (Booleans)
Für komplexe „zusammengesetzte“ Typen ist die Serialisierung komplizierter, da der zusammengesetzte Typ mehrere Elemente enthält, die unterschiedliche Typen oder unterschiedliche Größen oder beides haben können. Wenn diese Objekte alle feste Längen haben (d. h. die Größe der Elemente ist unabhängig von ihren tatsächlichen Werten immer konstant), ist die Serialisierung einfach eine Umwandlung jedes Elements im zusammengesetzten Typ, geordnet in Little-Endian-Byte-Strings. Diese Byte-Strings werden zusammengefügt. Das serialisierte Objekt hat die Bytelisten-Darstellung der Elemente mit fester Länge in derselben Reihenfolge, in der sie im deserialisierten Objekt erscheinen.
Bei Typen mit variablen Längen werden die tatsächlichen Daten durch einen „Offset“-Wert an der Position dieses Elements im serialisierten Objekt ersetzt. Die tatsächlichen Daten werden einem Heap am Ende des serialisierten Objekts hinzugefügt. Der Offset-Wert ist der Index für den Beginn der tatsächlichen Daten im Heap und fungiert als Zeiger auf die relevanten Bytes.
Das folgende Beispiel veranschaulicht, wie das Offsetting für einen Container mit Elementen sowohl fester als auch variabler Länge funktioniert:
12 struct Dummy {34 number1: u64,5 number2: u64,6 vector: Vec<u8>,7 number3: u648 }910 dummy = Dummy{1112 number1: 37,13 number2: 55,14 vector: vec![1,2,3,4],15 number3: 22,16 }1718 serialized = ssz.serialize(dummy)19Alle anzeigenserialized hätte die folgende Struktur (hier nur auf 4 Bits aufgefüllt, in der Realität auf 32 Bits aufgefüllt, und die int-Darstellung wird der Übersichtlichkeit halber beibehalten):
1[37, 0, 0, 0, 55, 0, 0, 0, 16, 0, 0, 0, 22, 0, 0, 0, 1, 2, 3, 4]2------------ ----------- ----------- ----------- ----------3 | | | | |4 number1 number2 offset for number 3 value for5 vector vector6der Übersichtlichkeit halber auf mehrere Zeilen aufgeteilt:
1[2 37, 0, 0, 0, # little-endian encoding of `number1`.3 55, 0, 0, 0, # little-endian encoding of `number2`.4 16, 0, 0, 0, # The "offset" that indicates where the value of `vector` starts (little-endian 16).5 22, 0, 0, 0, # little-endian encoding of `number3`.6 1, 2, 3, 4, # The actual values in `vector`.7]Dies ist immer noch eine Vereinfachung – die Ganzzahlen und Nullen in den obigen Schemata wären eigentlich gespeicherte Bytelisten, wie hier:
1[2 10100101000000000000000000000000 # little-endian encoding of `number1`3 10110111000000000000000000000000 # little-endian encoding of `number2`.4 10010000000000000000000000000000 # The "offset" that indicates where the value of `vector` starts (little-endian 16).5 10010110000000000000000000000000 # little-endian encoding of `number3`.6 10000001100000101000001110000100 # The actual value of the `bytes` field.7]Die tatsächlichen Werte für Typen mit variabler Länge werden also in einem Heap am Ende des serialisierten Objekts gespeichert, wobei ihre Offsets an den richtigen Positionen in der geordneten Liste der Felder gespeichert werden.
Es gibt auch einige Sonderfälle, die eine spezifische Behandlung erfordern, wie z. B. der Typ BitList, bei dem während der Serialisierung eine Längenbegrenzung hinzugefügt und bei der Deserialisierung entfernt werden muss. Vollständige Details sind in der SSZ-Spezifikation (opens in a new tab) verfügbar.
Deserialisierung
Um dieses Objekt zu deserialisieren, wird das Schema benötigt. Das Schema definiert das genaue Layout der serialisierten Daten, sodass jedes spezifische Element aus einem Byte-Blob in ein sinnvolles Objekt deserialisiert werden kann, bei dem die Elemente den richtigen Typ, Wert, die richtige Größe und Position haben. Es ist das Schema, das dem Deserialisierer mitteilt, welche Werte tatsächliche Werte und welche Offsets sind. Alle Feldnamen verschwinden, wenn ein Objekt serialisiert wird, werden aber bei der Deserialisierung gemäß dem Schema wieder instanziiert.
Siehe ssz.dev (opens in a new tab) für eine interaktive Erklärung dazu.
Merkelisierung
Dieses SSZ-serialisierte Objekt kann dann merkelisiert werden – das heißt, in eine Merkle-Baum-Darstellung derselben Daten umgewandelt werden. Zunächst wird die Anzahl der 32-Byte-Blöcke im serialisierten Objekt bestimmt. Dies sind die „Blätter“ (Leaves) des Baums. Die Gesamtzahl der Blätter muss eine Zweierpotenz sein, damit das gemeinsame Hashen der Blätter schließlich eine einzige Hash-Baum-Wurzel (Hash-Tree-Root) ergibt. Wenn dies von Natur aus nicht der Fall ist, werden zusätzliche Blätter hinzugefügt, die 32 Bytes an Nullen enthalten. Schematisch dargestellt:
1 hash tree root2 / \3 / \4 / \5 / \6 hash of leaves hash of leaves7 1 and 2 3 and 48 / \ / \9 / \ / \10 / \ / \11 leaf1 leaf2 leaf3 leaf4Alle anzeigenEs gibt auch Fälle, in denen sich die Blätter des Baums nicht von Natur aus so gleichmäßig verteilen wie im obigen Beispiel. Zum Beispiel könnte Blatt 4 ein Container mit mehreren Elementen sein, die zusätzliche „Tiefe“ erfordern, die dem Merkle-Baum hinzugefügt werden muss, wodurch ein ungleichmäßiger Baum entsteht.
Anstatt diese Baumelemente als Blatt X, Knoten X usw. zu bezeichnen, können wir ihnen verallgemeinerte Indizes geben, beginnend mit Wurzel = 1 und von links nach rechts entlang jeder Ebene zählend. Dies ist der oben erklärte verallgemeinerte Index. Jedes Element in der serialisierten Liste hat einen verallgemeinerten Index, der gleich 2**depth + idx ist, wobei idx seine nullbasierte Position im serialisierten Objekt ist und die Tiefe (depth) die Anzahl der Ebenen im Merkle-Baum ist, die als Zweierlogarithmus der Anzahl der Elemente (Blätter) bestimmt werden kann.
Verallgemeinerte Indizes
Ein verallgemeinerter Index ist eine Ganzzahl, die einen Knoten in einem binären Merkle-Baum darstellt, wobei jeder Knoten einen verallgemeinerten Index 2 ** depth + index in row hat.
1 1 --depth = 0 2**0 + 0 = 12 2 3 --depth = 1 2**1 + 0 = 2, 2**1+1 = 33 4 5 6 7 --depth = 2 2**2 + 0 = 4, 2**2 + 1 = 5...4Diese Darstellung liefert einen Knotenindex für jedes Datenelement im Merkle-Baum.
Multiproofs
Die Bereitstellung der Liste der verallgemeinerten Indizes, die ein bestimmtes Element darstellen, ermöglicht es uns, es gegen die Hash-Baum-Wurzel zu verifizieren. Diese Wurzel ist unsere akzeptierte Version der Realität. Alle Daten, die uns zur Verfügung gestellt werden, können gegen diese Realität verifiziert werden, indem sie an der richtigen Stelle in den Merkle-Baum eingefügt werden (bestimmt durch ihren verallgemeinerten Index) und beobachtet wird, dass die Wurzel konstant bleibt. Es gibt Funktionen in der Spezifikation hier (opens in a new tab), die zeigen, wie man die minimale Menge an Knoten berechnet, die erforderlich ist, um den Inhalt einer bestimmten Menge von verallgemeinerten Indizes zu verifizieren.
Um beispielsweise Daten im Index 9 im untenstehenden Baum zu verifizieren, benötigen wir den Hash der Daten an den Indizes 8, 9, 5, 3, 1. Der Hash von (8,9) sollte gleich dem Hash (4) sein, der mit 5 gehasht wird, um 2 zu erzeugen, was wiederum mit 3 gehasht wird, um die Baumwurzel 1 zu erzeugen. Wenn für 9 falsche Daten bereitgestellt würden, würde sich die Wurzel ändern – wir würden dies erkennen und den Zweig nicht verifizieren können.
1* = data required to generate proof23 1*4 2 3*5 4 5* 6 768* 9* 10 11 12 13 14 157