Přeskočit na hlavní obsah
Change page

Jednoduchá serializace

Stránka naposledy aktualizována: 1. února 2026

Jednoduchá serializace (SSZ) je metoda serializace používaná na Beacon Chainu. Nahrazuje RLP serializaci, která je používána na exekuční vrstvě a všude v rámci konsensuální vrstvy kromě protokolu pro objevování peerů. Více informací o serializaci RLP naleznete v článku Recursive-length prefix (RLP). SSZ je navržena tak, aby byla deterministická a také efektivně merkelizovatelná. Na SSZ můžeme pohlížet jako na dvě složky: Schéma serializace a schéma merkelizace, které je navrženo tak, aby efektivně fungovalo se serializovanou datovou strukturou.

Jak SSZ funguje?

Serializace

SSZ je schéma serializace, které není samo o sobě popisné - spíše spoléhá na schéma, které musí být předem známo. Cílem SSZ serializace je reprezentovat objekty libovolné složitosti jako řetězce bajtů. Tento proces je velmi jednoduchý pro "základní typy". Element je jednoduše převeden na hexadecimální bajty. Mezi základní typy patří:

  • celá čísla bez znaménka
  • booleovské hodnoty

Pro složité "kompozitní" typy je serializace složitější, protože kompozitní typ obsahuje více prvků, které mohou mít různé typy nebo různé velikosti, nebo obojí. V případech, kdy mají všechny tyto objekty pevnou délku (tj. velikost prvků bude vždy konstantní bez ohledu na jejich skutečné hodnoty), je serializace jednoduše konverzí každého prvku ve složeném typu, seřazeného do little-endian bajtových řetězců. Tyto bajtové řetězce jsou spojeny dohromady. Serializovaný objekt má bytelistovou reprezentaci prvků s pevnou délkou ve stejném pořadí, v jakém se objevují v deserializovaném objektu.

Pro typy s proměnlivou délkou se skutečná data nahrazují hodnotou "offsetu" na pozici toho prvku v serializovaném objektu. Skutečná data se přidávají do zásobníku na konci serializovaného objektu. Hodnota offsetu je indexem pro začátek skutečných dat v zásobníku, což funguje jako ukazatel na příslušné bajty.

Následující příklad ilustruje, jak funguje offsetování pro kontejner s prvky s pevnou i proměnlivou délkou:

1
2 struct Dummy {
3
4 number1: u64,
5 number2: u64,
6 vector: Vec<u8>,
7 number3: u64
8 }
9
10 dummy = Dummy{
11
12 number1: 37,
13 number2: 55,
14 vector: vec![1,2,3,4],
15 number3: 22,
16 }
17
18 serialized = ssz.serialize(dummy)
19
Zobrazit vše

serialized by mělo následující strukturu (zde pouze doplněno na 4 bity, ve skutečnosti doplněno na 32 bitů a pro přehlednost je zachována reprezentace int):

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 posun pro number 3 hodnota pro
5 vektor vektor
6

pro přehlednost je rozdělíme do řádků:

1[
2 37, 0, 0, 0, # kódování `number1` v pořadí little-endian.
3 55, 0, 0, 0, # kódování `number2` v pořadí little-endian.
4 16, 0, 0, 0, # „Posun“, který udává, kde začíná hodnota `vector` (little-endian 16).
5 22, 0, 0, 0, # kódování `number3` v pořadí little-endian.
6 1, 2, 3, 4, # Skutečné hodnoty ve `vector`.
7]

Toto je stále zjednodušení - celá čísla a nuly ve schématech výše by ve skutečnosti byly uloženy bytelisty, jako je tento:

1[
2 10100101000000000000000000000000 # kódování `number1` v pořadí little-endian
3 10110111000000000000000000000000 # kódování `number2` v pořadí little-endian.
4 10010000000000000000000000000000 # „Posun“, který udává, kde začíná hodnota `vector` (little-endian 16).
5 10010110000000000000000000000000 # kódování `number3` v pořadí little-endian.
6 10000001100000101000001110000100 # Skutečná hodnota pole `bytes`.
7]

Takže skutečné hodnoty pro typy s proměnlivou délkou jsou uloženy v zásobníku na konci serializovaného objektu, přičemž jejich offsety jsou uloženy na správných pozicích v uspořádaném seznamu polí.

Existují také některé speciální případy, které vyžadují specifické zacházení, například typ BitList, který vyžaduje přidání délkového limitu během serializace a jeho odstranění během deserializace. Veškeré podrobnosti jsou k dispozici ve specifikaci SSZ (opens in a new tab).

Deserializace

K deserializaci tohoto objektu je zapotřebí schéma. Schéma definuje přesné uspořádání serializovaných dat tak, aby každý konkrétní prvek mohl být deserializován z blobu bajtů do nějakého smysluplného objektu s prvky majícími správný typ, hodnotu, velikost a pozici. Je to právě schéma, které říká deserializátoru, které hodnoty jsou skutečné hodnoty a které jsou offsety. Všechny názvy polí zmizí, když je objekt serializován, ale při deserializaci se podle schématu znovu vytvoří.

Pro interaktivní vysvětlení se podívejte na ssz.dev (opens in a new tab).

Merkleizace

Tento SSZ serializovaný objekt pak může být merkelizován - to znamená transformován do Merkleova stromu reprezentujícího stejná data. Nejprve se určí počet 32bajtových bloků v serializovaném objektu. Tyto bloky tvoří „listy“ stromu. Celkový počet listů musí být mocnina dvou, aby hašování listů nakonec vytvořilo jediný hašový kořen stromu. Pokud tomu tak přirozeně není, jsou přidány další listy obsahující 32 bajtů nul. Schématicky:

1 kořen hašového stromu
2 / \
3 / \
4 / \
5 / \
6 haš listů haš listů
7 1 a 2 3 a 4
8 / \ / \
9 / \ / \
10 / \ / \
11 list1 list2 list3 list4
Zobrazit vše

Existují také případy, kdy se listy stromu přirozeně nerozdělují rovnoměrně, jako tomu bylo v předchozím příkladu. Například list 4 by mohl být kontejner s více prvky, které vyžadují přidání další "hloubky" do Merkle tree, čímž se vytvoří nerovnoměrný strom.

Místo toho, abychom tyto prvky stromu označovali jako list X, uzel X atd., můžeme jim přiřadit zobecněné indexy, počínaje kořenem = 1 a číslováním zleva doprava na každé úrovni. Toto je zobecněný index, který jsme vysvětlili výše. Každý prvek v serializovaném seznamu má zobecněný index rovný 2**depth + idx, kde idx je jeho pozice s indexem nula v serializovaném objektu a hloubka je počet úrovní v Merkle tree, který lze určit jako logaritmus počtu prvků (listů) při základu 2.

Zobecněné indexy

Zobecněný index je celé číslo, které představuje uzel v binárním Merkle tree, kde každý uzel má zobecněný index 2 ** depth + index in row.

1 1 --hloubka = 0 2**0 + 0 = 1
2 2 3 --hloubka = 1 2**1 + 0 = 2, 2**1+1 = 3
3 4 5 6 7 --hloubka = 2 2**2 + 0 = 4, 2**2 + 1 = 5...
4

Toto zobrazení poskytuje index uzlu pro každý datový prvek v Merkletree.

Multiproofy

Poskytnutí seznamu zobecněných indexů představujících konkrétní prvek nám umožňuje jej ověřit vůči hašovému kořenovému stromu. Tento kořen je naším přijatým zobrazením reality. Jakákoli data, která nám jsou poskytnuta, mohou být ověřena vůči této realitě vložením do správného místa v Merkle tree (určeného jeho zobecněným indexem) a pozorováním, zda kořen zůstává konstantní. Ve specifikaci zde (opens in a new tab) jsou funkce, které ukazují, jak vypočítat minimální sadu uzlů potřebnou k ověření obsahu konkrétní sady zobecněných indexů.

Například k ověření dat na indexu 9 v níže uvedeném stromu potřebujeme haš dat na indexech 8, 9, 5, 3, 1. Haš (8,9) by měl být roven haši (4), který se hašuje s 5 a vytváří 2, který se hašuje s 3 a vytváří kořen stromu 1. Pokud by byla poskytnuta nesprávná data pro 9, kořen by se změnil – to bychom zjistili a ověření větve by selhalo.

1* = data potřebná k vygenerování důkazu
2
3 1*
4 2 3*
5 4 5* 6 7
68* 9* 10 11 12 13 14 15
7

Další čtení

Byl tento článek užitečný?