Dynamic SSZ is a production-ready Go library for SSZ serialization, hashing, and Merkle proofs. Its distinguishing feature is support for runtime-determined field sizes: the same Go types work across different network presets (mainnet, minimal, custom testnets) by resolving size limits from a spec configuration at runtime. It combines instant reflection-based processing for flexibility with optional static code generation for maximum performance.
dynssz-size/dynssz-max tags resolve limits from runtime spec values, with full math expression support (e.g. (EPOCHS_PER_HISTORICAL_VECTOR*SLOTS_PER_EPOCH)/8)dynssz-gen CLI or programmatic API (2-3x faster than reflection), configurable through flags or YAML config filesio.Reader/io.Writer for large objectsnohashtree build tag), and opt-in background subtree reduction (WithAsyncHashing) for large objectsGetTree), generate single proofs and compressed multiproofs by generalized index, and verify them with the treeproof packageTypeWrapper[Descriptor, []uint64]ssz-bitsize), uint128/uint256, multi-dimensional arrays with per-dimension limits, time.Time, and auto-detection of common types (holiman/uint256, go-bitfield)ssz-index (EIP-7495)Union[T] (classic spec, incl. the None option) and CompatibleUnion[T] (EIP-8016) typesbig.Int, and optional types (non-standard, disabled by default)Both processing paths are production ready. The reflection-based code is battle-tested across various Ethereum tooling, and the code generator is feature complete and continuously verified through differential fuzzing against the reflection implementation and the consensus spec tests.
The library follows semantic versioning: breaking changes only occur across major versions, bigger features land in minor versions, and patch versions contain bugfixes and small improvements. Within the same major version, the public API and the behavior of generated code remain backward compatible β code generated by every previous v1.x release is archived in codegen/compat-tests and actively checked by the CI on every change. See the security policy for supported versions and vulnerability reporting.
go get github.com/pk910/dynamic-ssz
import dynssz "github.com/pk910/dynamic-ssz"
// Define your types with SSZ tags
type MyStruct struct {
FixedArray [32]byte
DynamicList []uint64 `ssz-max:"1000"`
ConfigBased []byte `ssz-max:"1024" dynssz-max:"MAX_SIZE"`
}
// Create a DynSsz instance with your configuration
specs := map[string]any{
"MAX_SIZE": uint64(2048),
}
ds := dynssz.NewDynSsz(specs)
// Marshal
data, err := ds.MarshalSSZ(myObject)
// Unmarshal
err = ds.UnmarshalSSZ(&myObject, data)
// Hash Tree Root
root, err := ds.HashTreeRoot(myObject)
// Merkle tree & proofs
tree, err := ds.GetTree(myObject)
proof, err := tree.Prove(generalizedIndex)
The ssz-max and dynssz-max tags work together: ssz-max provides a static fallback, while dynssz-max references a spec value resolved at runtime. If the spec value is available it overrides the static default; otherwise the static value is used. This lets the same types work across different network presets (mainnet, minimal, custom testnets).
For maximum performance, use code generation with the dynssz-gen CLI tool:
go install github.com/pk910/dynamic-ssz/dynssz-gen@latest
Generate SSZ methods:
# Generate for types in current package
dynssz-gen -package . -types "MyStruct,OtherType" -output generated.go
# Generate for types in external package
dynssz-gen -package github.com/example/types -types "Block" -output block_ssz.go
# Or drive everything from a YAML config file
dynssz-gen -config dynssz.yaml
Generated code produces optimized SSZ methods that eliminate reflection overhead. Important: Always use ds.MarshalSSZ(), ds.UnmarshalSSZ(), etc. as your entry points - the runtime automatically delegates to generated methods when available. Do not call generated methods (like MarshalSSZDyn) directly, as this creates a circular dependency that prevents regeneration. See the Code Generation Guide for details.
Dynamic SSZ is benchmarked against other SSZ libraries (including fastssz) in a dedicated benchmark repository: pk910/ssz-benchmark (view graphs).
The benchmarks compare encoding, decoding, and hash tree root performance across different SSZ libraries using common Ethereum consensus data structures.
View interactive benchmark results and historical trends at: https://pk910.github.io/ssz-benchmark/
The library includes comprehensive testing infrastructure:
Check out the examples directory for standalone, CI-tested example projects:
DynamicHashRoot delegation hookWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
Dynamic SSZ is licensed under the Apache 2.0 License.
Overall
Maintained
Code review
Contributors
Go module
Latest release
Releases this year
Dynamic SSZ is a production-ready Go library for SSZ serialization, hashing, and Merkle proofs. Its distinguishing feature is support for runtime-determined field sizes: the same Go types work across different network presets (mainnet, minimal, custom testnets) by resolving size limits from a spec configuration at runtime. It combines instant reflection-based processing for flexibility with optional static code generation for maximum performance.
dynssz-size/dynssz-max tags resolve limits from runtime spec values, with full math expression support (e.g. (EPOCHS_PER_HISTORICAL_VECTOR*SLOTS_PER_EPOCH)/8)dynssz-gen CLI or programmatic API (2-3x faster than reflection), configurable through flags or YAML config filesio.Reader/io.Writer for large objectsnohashtree build tag), and opt-in background subtree reduction (WithAsyncHashing) for large objectsGetTree), generate single proofs and compressed multiproofs by generalized index, and verify them with the treeproof packageTypeWrapper[Descriptor, []uint64]ssz-bitsize), uint128/uint256, multi-dimensional arrays with per-dimension limits, time.Time, and auto-detection of common types (holiman/uint256, go-bitfield)ssz-index (EIP-7495)Union[T] (classic spec, incl. the None option) and CompatibleUnion[T] (EIP-8016) typesbig.Int, and optional types (non-standard, disabled by default)Both processing paths are production ready. The reflection-based code is battle-tested across various Ethereum tooling, and the code generator is feature complete and continuously verified through differential fuzzing against the reflection implementation and the consensus spec tests.
The library follows semantic versioning: breaking changes only occur across major versions, bigger features land in minor versions, and patch versions contain bugfixes and small improvements. Within the same major version, the public API and the behavior of generated code remain backward compatible β code generated by every previous v1.x release is archived in codegen/compat-tests and actively checked by the CI on every change. See the security policy for supported versions and vulnerability reporting.
go get github.com/pk910/dynamic-ssz
import dynssz "github.com/pk910/dynamic-ssz"
// Define your types with SSZ tags
type MyStruct struct {
FixedArray [32]byte
DynamicList []uint64 `ssz-max:"1000"`
ConfigBased []byte `ssz-max:"1024" dynssz-max:"MAX_SIZE"`
}
// Create a DynSsz instance with your configuration
specs := map[string]any{
"MAX_SIZE": uint64(2048),
}
ds := dynssz.NewDynSsz(specs)
// Marshal
data, err := ds.MarshalSSZ(myObject)
// Unmarshal
err = ds.UnmarshalSSZ(&myObject, data)
// Hash Tree Root
root, err := ds.HashTreeRoot(myObject)
// Merkle tree & proofs
tree, err := ds.GetTree(myObject)
proof, err := tree.Prove(generalizedIndex)
The ssz-max and dynssz-max tags work together: ssz-max provides a static fallback, while dynssz-max references a spec value resolved at runtime. If the spec value is available it overrides the static default; otherwise the static value is used. This lets the same types work across different network presets (mainnet, minimal, custom testnets).
For maximum performance, use code generation with the dynssz-gen CLI tool:
go install github.com/pk910/dynamic-ssz/dynssz-gen@latest
Generate SSZ methods:
# Generate for types in current package
dynssz-gen -package . -types "MyStruct,OtherType" -output generated.go
# Generate for types in external package
dynssz-gen -package github.com/example/types -types "Block" -output block_ssz.go
# Or drive everything from a YAML config file
dynssz-gen -config dynssz.yaml
Generated code produces optimized SSZ methods that eliminate reflection overhead. Important: Always use ds.MarshalSSZ(), ds.UnmarshalSSZ(), etc. as your entry points - the runtime automatically delegates to generated methods when available. Do not call generated methods (like MarshalSSZDyn) directly, as this creates a circular dependency that prevents regeneration. See the Code Generation Guide for details.
Dynamic SSZ is benchmarked against other SSZ libraries (including fastssz) in a dedicated benchmark repository: pk910/ssz-benchmark (view graphs).
The benchmarks compare encoding, decoding, and hash tree root performance across different SSZ libraries using common Ethereum consensus data structures.
View interactive benchmark results and historical trends at: https://pk910.github.io/ssz-benchmark/
The library includes comprehensive testing infrastructure:
Check out the examples directory for standalone, CI-tested example projects:
DynamicHashRoot delegation hookWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
Dynamic SSZ is licensed under the Apache 2.0 License.
Overall
Maintained
Code review
Contributors
Go module
Latest release
Releases this year