github.com/protolambda/zssz@v0.1.5/README.md (about)

     1  # ZSSZ
     2  
     3  Highly optimized SSZ encoder. Uses unsafe Go, safely.
     4  
     5  "ZSSZ", a.k.a. ZRNT-SSZ, is the SSZ version used and maintained for [ZRNT](https://github.com/protolambda/zrnt),
     6   the ETH 2.0 Go executable spec.
     7  
     8  
     9  Features:
    10  - Zero-allocations where possible
    11     - offset checking allocates a small array of `uint32`s
    12     - dynamic size uses buffer pooling to encode dynamic data *in order*
    13     - small value passing is preferred over passing slices, avoid memory on the heap. 
    14  - Construct all encoding/decoding/hashing logic for a type, then run it 10000 times the efficient way
    15  - No reflection during encoding/decoding/hashing execution of the constructed SSZ-type
    16      - Exception: slice allocation uses `reflect.MakeSlice`, and pointer-value allocation uses `reflect.New`,
    17        but the type is already readily available.
    18        This is to avoid the GC collecting allocated space within a slice, and be safe on memory assumptions.
    19  - Bitfields/Bitlists are compatible with byte arrays, and not unpacked into boolean arrays.
    20  - Stable merkleization, using the v0.8 limit-based merkleization depth for lists.
    21  - Construction of SSZ types can also be used to support encoding of dynamic types
    22  - Support for struct embedding & squashing.
    23  - No dependencies other than the standard Go libs.
    24      - Zero-hashes are pre-computed with the `sha256` package,
    25         but you can supply a more efficient version to run hash-tree-root with. 
    26         (E.g. reduce allocations by re-using a single state)
    27  - Hardened: A work in progress now, but all SSZ rules are strictly yet efficiently enforced.
    28  - Fuzzmode-decoding: decode arbitrary data into a struct.
    29    The length of the input + contents determine the length of dynamic parts.
    30  - Replaceable hash-function. Initialize the pre-computed zero-hashes with `InitZeroHashes(yourHashFn)`
    31    and then call `HashTreeRoot(yourHashFn, val, sszType)`. Zero-hashes default to SHA-256.
    32  - Passes the Eth 2.0 Static-SSZ tests, in the [ZRNT](https://github.com/protolambda/zrnt) test suite.
    33  - Ongoing real-world benchmarking effort for use in ZRNT.
    34  - No caching implemented, *yet*. Work in progress, in open collaboration with ETH 2.0 client implementers.
    35  
    36  Supported types
    37  - small basic-types (`bool`, `uint8`, `uint16`, `uint32`, `uint64`)
    38  - containers
    39    - squash non-pointer struct-fields with a tag `ssz:"squash"`, or embed the struct.
    40      Note: just like field names must be public, embedded structs must be a public type. 
    41  - vector
    42    - `Vector`, optimized for fixed and variable size elements
    43    - `BasicVector`, optimized for basic element types
    44    - `BytesN`, optimized for bytes vector (fixed length)
    45    - `Bitvector`, bits packed in a byte array.
    46  - list
    47    - `List` optimized for fixed and variable size elements
    48    - `BasicList`, optimized for basic element types
    49    - `Bytes`, optimized for bytes list (dynamic length)
    50    - `Bitlist`, bits packed in a byte slice, with bit delimiter to determine length.
    51  
    52  Possibly supported in future:
    53  - union/null
    54  - embedding of pointer structs (suggestions for nil-semantics welcome)
    55  - uint128/uint256
    56  - strings
    57  - partials
    58  
    59  
    60  ## Usage
    61  
    62  Here is an example that illustrates the full usage:
    63  ```go
    64  package main
    65  
    66  import (
    67  	. "github.com/protolambda/zssz/types"
    68  	. "github.com/protolambda/zssz/htr"
    69  	. "github.com/protolambda/zssz"
    70  	"reflect"
    71  	"bytes"
    72  	"bufio"
    73  	"fmt"
    74  	"crypto/sha256"
    75  )
    76  
    77  type Bar struct {
    78  	DogeImg []byte
    79  	Timestamp uint64
    80  	ID [32]byte
    81  }
    82  
    83  type MyThing struct {
    84  	Foo uint32
    85  	Bars [2]Bar
    86  	SomeSignature [96]byte
    87  }
    88  
    89  func main() {
    90  	myThingSSZ := GetSSZ((*MyThing)(nil))
    91  
    92  	// example instance filled with dummy data
    93  	obj := MyThing{
    94  		Foo: 123,
    95  		Bars: [2]Bar{
    96  			{DogeImg: []byte{1, 2, 3}, Timestamp: 0x112233, ID: [32]byte{1}},
    97  			{DogeImg: []byte{7, 8, 9}, Timestamp: 12345678, ID: [32]byte{2}},
    98  		},
    99  		SomeSignature: [96]byte{0,1,2,3,4,5,6,7,8,9},
   100  	}
   101  
   102  	// encoding
   103  	// -----------------------
   104  	var buf bytes.Buffer
   105  	bufWriter := bufio.NewWriter(&buf)
   106  	// Note that Encode takes any io.Writer
   107  	if err := Encode(bufWriter, &obj, myThingSSZ); err != nil {
   108  		panic(err)
   109  	}
   110  	if err := bufWriter.Flush(); err != nil {
   111  		panic(err)
   112  	}
   113  	data := buf.Bytes()
   114  	fmt.Printf("encoded myThing: %x\n", data)
   115  
   116  	// decoding
   117  	// -----------------------
   118  	dst := MyThing{}
   119  	bytesLen := uint32(len(data))
   120      r := bytes.NewReader(data)
   121  	// note that Decode takes any io.Reader
   122  	if err := Decode(r, bytesLen, &dst, myThingSSZ); err != nil {
   123  		panic(err)
   124  	}
   125  	fmt.Printf("decoded myThing: some data from it: %v\n", dst.Bars[1].DogeImg[:])
   126  
   127  	// hash-tree-root
   128  	// -----------------------
   129  	// get the root
   130  	root := HashTreeRoot(sha256.Sum256, &obj, myThingSSZ)
   131  	fmt.Printf("root of my thing: %x\n", root)
   132  	
   133  	signingRoot := SigningRoot(sha256.Sum256, &obj, myThingSSZ.(SignedSSZ))
   134  	fmt.Printf("signing-root of my thing: %x\n", signingRoot)
   135  }
   136  ```
   137  
   138  ## Format
   139  
   140  ### Basic types
   141  
   142  SSZ basic types match the Go types, with the exception of `uint128` and `uint256`, which are not supported.
   143  As a replacement, these can be declared as `[16]byte` and `[32]byte`, and will encode/decode/hash the same.
   144  
   145  ### Lists
   146  
   147  Lists types are required to define a limit.
   148  For sanity in an adversarial environment, and stable merkleization.
   149  
   150  Since Go does not support parametrizing types, meta-classes, or any static class functionality,
   151  this is slightly non-standard: by defining a pointer-receiver method on the type,
   152  we can return the (static) limit information, even if the pointer itself is nil.
   153  This enables ZSSZ to derive the data from a typed nil-pointer.
   154  
   155  This pattern also enables to deal with the list like any other slice,
   156   and not create custom constructor/parsers/serializers for it elsewhere.
   157  
   158  Example:
   159  ```go
   160  type MyList []Something
   161  
   162  func (_ *MyList) Limit() uint32 {
   163  	return 256
   164  }
   165  
   166  type SomeBytes []byte
   167  
   168  func (_ *SomeBytes) Limit() uint32 {
   169  	return 1024
   170  }
   171  
   172  type RootList [][32]byte
   173  
   174  func (_ *RootList) Limit() uint32 {
   175  	return 1 << 13
   176  }
   177  ```
   178  
   179  ### Containers
   180  
   181  Containers are just structs. Fields can be omitted by adding `ssz:"omit"` as struct-field tag.
   182  
   183  ```go
   184  type MyContainer struct {
   185  	Foo uint64
   186  	Cache NonSSZThing `ssz:"omit"`
   187  	Bar OtherContainer
   188  	Abc MyList
   189  }
   190  ```
   191  
   192  ### Bitfields
   193  
   194  Series of bools are too inefficient, hence SSZ defines a way to pack bools in a bitfield.
   195  This memory structure is simple and easily implemented in Go, hence the choice to not unpack into a bool array.
   196  This preserves all compatibility with byte arrays, which is nice for debugging, 
   197   and using other serialization formats without any special encoders or wrappers.
   198  
   199  The `bitfiels` package contains interfaces and helper methods to implement the essential
   200   bitfield functionality in your own bitvector/bitlist types.
   201  
   202  #### Bitvectors
   203  
   204  Bitvectors are defined as byte vectors, tagged with some extra information (a `BitLen() uint32` function).
   205  
   206  Example:
   207  
   208  ```go
   209  type Bitvec4 [1]byte
   210  
   211  func (_ *Bitvec4) BitLen() uint32 { return 4 }
   212  
   213  const MY_LARGE_BITVECTOR_SIZE = 10000
   214  
   215  type LargeBitfield [(MY_LARGE_BITVECTOR_SIZE + 7) / 8]byte
   216  
   217  func (_ *LargeBitfield) BitLen() uint32 { return MY_LARGE_BITVECTOR_SIZE }
   218  ```
   219  
   220  #### Bitlists
   221  
   222  Bitlists are like bitvectors, but based off of byte slices instead.
   223  Bitlists are dynamic in size, and hence need a limit, like any other list type.
   224  To determine the length in bits, the last byte of a bitlist has a trailing (high end of byte) `1` bit 
   225  that functions as delimiter. There is an utility function to read it.
   226  
   227  Example:
   228  
   229  ```go
   230  type bitlist16 []byte
   231  
   232  func (_ *bitlist16) Limit() uint32 { return 16 }
   233  func (b bitlist16) BitLen() uint32 { return bitfields.BitlistLen(b) }
   234  ```
   235  
   236  ### Union
   237  
   238  Not yet supported. Suggestions for Go-style are welcome.
   239  
   240  
   241  ## Extending
   242  
   243  ZRNT-SSZ does not check for interfaces on types to do custom SSZ encoding etc., as this would be to slow and inflexible.
   244  
   245  Instead, it gives you the freedom to compose your own custom SSZ type-factory,
   246   the function that is used to compose a `SSZ` structure.
   247  
   248  The default factory function is building the structure when calling `GetSSZ((*SomeType)(nil))`
   249  
   250  With this, you can remove/change existing functionality, and add your own. 
   251  Pure composition, without performance degradation 
   252  (composition happens before actual execution of SSZ serialization/hashing).
   253  
   254  
   255  ### Composition example
   256  
   257  ```go
   258  package main
   259  
   260  import (
   261  	. "github.com/protolambda/zssz/types"
   262  	"reflect"
   263  )
   264  
   265  // A simple interface to call when composing a SSZ for your type.
   266  func MyFactory(typ reflect.Type) (SSZ, error) {
   267  	// Pass this same factory to it, this
   268  	return myFactory(MyFactory, typ)
   269  }
   270  
   271  // other factories may want to use this one for some of their functionality, make it public.
   272  func MyFactoryFn(factory SSZFactoryFn, typ reflect.Type) (SSZ, error) {
   273  	// figure out which SSZ type definition to use, based on interface check
   274  	if typ.Implements(myFancyCustomTypeInterface) {
   275  		// pass it the typ, it's recommended to make the function check if the type is really allowed.
   276  		// This makes usage by other 3rd-party factories safe.
   277  		return FancyTypeSSZ(typ)
   278  	}
   279  	
   280  	// figure out which SSZ type definition to use, based on kind
   281  	switch typ.Kind() {
   282  	case reflect.Struct:
   283  		// pass it a factory, to build SSZ definitions for child-types (container fields).
   284  		return MyContainerSSZDefinition(factory, typ)
   285  	default:
   286  		// use the default behavior otherwise
   287  		return DefaultSSZFactory(factory, typ)
   288  	}
   289  }
   290  
   291  func main() {
   292  	// building a SSZ type definition using a factory
   293  	myThingSSZ, _ := MySSZFactory(reflect.TypeOf((*MyThing)(nil)).Elem())
   294  }
   295  ```
   296  
   297  ### Writing a custom SSZ type definition
   298  
   299  Simply implement the `SSZ` interface. And provide some function to instantiate it (See `Vector` as example),
   300   or maybe declare a singleton instance (See `ssz_basic` as example).
   301  You can then mix it in however you like with the factory pattern, and start serializing/hashing your way.
   302  
   303  
   304  ## Contact
   305  
   306  Dev: [@protolambda on Twitter](https://twitter.com/protolambda)
   307  
   308  
   309  ## License
   310  
   311  MIT, see license file.
   312