SSZ (Simple Serialize) was coined by Vitalik Buterin in 2018 as a simpler replacement for RLP in the Beacon Chain proposal.
Justin Drake, Ben Edgington, and the research team iterated in the early eth2.0-specs repo; Drake’s PR #71 is the first time
clients saw theSimpleSerializename in code. The work later migrated into the current consensus-specs/ssz folder and has stayed there ever since.
The official spec shows how every basic type is defined. The excerpt below is taken straight from the GitHub file that client teams track:
assert N in [8, 16, 32, 64, 128, 256]
return value.to_bytes(N // BITS_PER_BYTE, "little")
assert value in (True, False)
return b"\x01" if value is True else b"\x00"While both SSZ and RLP ultimately serialize data into bytes, their approaches and goals differ significantly. SSZ is designed around strict typing and fixed layouts to enable fast hashing and simple proofs, whereas RLP focuses on flexible, self-describing length-prefixed encodings optimized for general-purpose data serialization.
RLP encodes each field with length prefixes, recursively nesting lists and byte arrays.
SSZ encodes fixed-size fields sequentially, followed by 4-byte offsets pointing to variable-size fields in the variable section.
This typed offset scheme enables constant-time access to variable fields and efficient hashing.
For multi-byte strings, RLP adds a length prefix to indicate the payload size, while SSZ encodes the bytes directly according to its type definition. Adjust the slider below to see how each encoding handles strings of different lengths.
When encoding a sequence of bytes (a list), RLP wraps the entire list with a length prefix, and each element may also have its own prefix. SSZ simply concatenates the elements without any prefixes, relying on the type definition to know the structure. Try entering different values below to compare the encodings side-by-side.
Ethereum uses RLP primarily for the execution layer, encoding transactions and state data, where flexibility is key. SSZ is the backbone of the consensus layer, chosen for its deterministic layouts and suitability for light-client proofs.
Unlike RLP, SSZ does not encode type information in the byte stream. The layout (type definition) is separate from the encoded data and must be known in advance by both encoder and decoder.
Where type definitions are stored:
BeaconBlock, Attestation, Validator).Why this design?
When you see List[uint8, 8] or Vector[uint16, 4], these are part of the type definition, not the encoded bytes. The bytes themselves contain only the data, arranged according to the predefined layout.
uint8 → 1 byte, uint16 → 2 bytes, uint32 → 4 bytes.0x00 for false, 0x01 for true).Switch the type selector, nudge the value, and notice how the least-significant byte always appears first.
Vectors are arrays with the length baked into the type. Because each element has the same size, the encoder simply places them back-to-back. Nothing else—no headers, no lengths.
Changing any slot only touches its two bytes. The total stays at 8 bytes because 4 * uint16 never changes.
Lists trade strictness for flexibility. Elements are still written consecutively, but the container that owns the list must keep track of where those bytes live. For a list of basic types we can infer the length from the byte count because one element = one byte.
Try entering more than eight numbers—the extra entries are ignored because the max length is part of the type.
A container feels like a struct with named fields. SSZ splits the encoding into two areas:
List[uint8, 6]
List[uint16, 3]
The demo container has version, flag, count, and two lists. As you extend a list: