Ethereum 2.0 Serialization: Simple Serialize (SSZ)

Simple Serialize is the selected serialization algorithm for all data structures common across Ethereum 2.0 client implementations. It is specified in the official Ethereum 2.0 specification. The docs below outline how to use Simple Serialize and its external methods:

Interface

Encodable

A type is Encodable if it implements EncodeSSZ and EncodeSSZSize function.

type Encodable interface {
EncodeSSZ(io.Writer) error
// Estimate the encoding size of the object without doing the actual encoding
EncodeSSZSize() (uint32, error)
}

Decodable

A type is Decodable if it implements DecodeSSZ().

type Decodable interface {
DecodeSSZ(io.Reader) error
}

API

Encoding function

// Encode val and output the result into w.
func Encode(w io.Writer, val interface{}) error
// EncodeSize returns the target encoding size without doing the actual encoding.
// This is an optional pass. You don't need to call this before the encoding unless you
// want to know the output size first.
func EncodeSize(val interface{}) (uint32, error)

Decoding function

// Decode data read from r and output it into the object pointed by pointer val.
func Decode(r io.Reader, val interface{}) error

Usage

Say you have a struct like this

type exampleStruct1 struct {
Field1 uint8
Field2 []byte
}
`

You implement the Encoding interface for it:

func (e *exampleStruct1) EncodeSSZ(w io.Writer) error {
return Encode(w, *e)
}
‚Äč
func (e *exampleStruct1) EncodeSSZSize() (uint32, error) {
return EncodeSize(*e)
}

Now you can encode this object like this

e1 := &exampleStruct1{
Field1: 10,
Field2: []byte{1, 2, 3, 4},
}
wBuf := new(bytes.Buffer)
if err = e1.EncodeSSZ(wBuf); err != nil {
return fmt.Errorf("failed to encode: %v", err)
}
encoding := wBuf.Bytes() // encoding becomes [0 0 0 9 10 0 0 0 4 1 2 3 4]

You can also get the estimated encoding size

var encodeSize uint32
if encodeSize, err = e1.EncodeSSZSize(); err != nil {
return fmt.Errorf("failed to get encode size: %v", err)
}
// encodeSize becomes 13

Similarly, you can implement the Decodable interface for this struct

func (e *exampleStruct1) DecodeSSZ(r io.Reader) error {
return Decode(r, e)
}

Now you can decode to create new struct

e2 := new(exampleStruct1)
rBuf := bytes.NewReader(encoding)
if err = e2.DecodeSSZ(rBuf); err != nil {
return fmt.Errorf("failed to decode: %v", err)
}
// e2 now has the same content as e1

Notes

Supported data types

  • uint8

  • uint16

  • uint32

  • uint64

  • slice

  • array

  • struct

  • pointer