treeproof

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package treeproof provides Merkle tree construction and proof generation for SSZ structures.

This package enables the construction of complete Merkle trees from SSZ-encoded data structures, supporting both traditional binary trees and progressive trees for advanced use cases. It provides functionality for generating and verifying Merkle proofs against generalized indices.

Key features:

  • Binary tree construction: Standard SSZ merkleization for fixed-size containers
  • Progressive tree construction: Advanced merkleization for containers with optional fields
  • Proof generation: Single and multi-proof generation for any tree node
  • Proof verification: Standalone verification of generated proofs
  • Tree visualization: Debug-friendly tree structure display with generalized indices

The package supports the generalized index system used in Ethereum 2.0 for addressing nodes within Merkle trees, enabling efficient proof generation for any field or value within complex data structures.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func VerifyMultiproof

func VerifyMultiproof(root []byte, proof, leaves [][]byte, indices []int) (bool, error)

VerifyMultiproof verifies a proof for multiple leaves against the given root.

The arguments provided to this function need to adhere to some ordering rules, otherwise a successful verification is not guaranteed even if the client holds correct data:

1. `leaves` and `indices` have same order, i.e. `leaves[i]` is the leaf at `indices[i]`;

2. `proofs` are sorted in descending order according to their generalised indices.

For a better understanding of "2.", consider the following the tree:

       .
   .       .            * = intermediate hash (i.e. an element of `proof`)
 .   *   *   .          x = leaf
x x . . . . x *

In the example above, we have three intermediate hashes in position 5, 6 and 15. Let's call such hashes "*5", "*6" and "*15" respectively. Then, when calling this function `proof` should be ordered as [*15, *6, *5].

func VerifyProof

func VerifyProof(root []byte, proof *Proof) (bool, error)

VerifyProof verifies a single merkle branch. It's more efficient than VerifyMultiproof for proving one leaf.

Types

type CompressedMultiproof

type CompressedMultiproof struct {
	Indices    []int
	Leaves     [][]byte
	Hashes     [][]byte
	ZeroLevels []int // Stores the level for every omitted zero hash in the proof
}

CompressedMultiproof represents a compressed merkle proof of several leaves. Compression is achieved by omitting zero hashes (and their hashes). `ZeroLevels` contains information which helps the verifier fill in those hashes.

func (*CompressedMultiproof) Decompress

func (c *CompressedMultiproof) Decompress() *Multiproof

Decompress returns a new multiproof, filling in the omitted zero hashes. See `CompressedMultiProof` for more info.

type Multiproof

type Multiproof struct {
	Indices []int    // Generalized indices of proven leaves
	Leaves  [][]byte // 32-byte leaf values (ordered by Indices)
	Hashes  [][]byte // Shared verification hashes
}

Multiproof represents an efficient Merkle proof for multiple leaves.

Instead of generating separate proofs for each leaf, a multiproof consolidates the verification data by sharing common intermediate hashes. This is more efficient when proving multiple values from the same tree.

Fields:

  • Indices: The generalized indices of all leaves being proven
  • Leaves: The 32-byte values at the specified indices (same order as Indices)
  • Hashes: Shared set of hashes needed to verify all leaves

func (*Multiproof) Compress

func (p *Multiproof) Compress() *CompressedMultiproof

Compress returns a new proof with zero hashes omitted. See `CompressedMultiproof` for more info.

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node represents a single node in a Merkle tree constructed from SSZ data.

Each node can be either a leaf node (containing actual data) or a branch node (containing the hash of its children). The tree structure follows SSZ merkleization rules and supports both binary and progressive tree layouts.

For leaf nodes:

  • left and right are nil
  • value contains the 32-byte leaf data

For branch nodes:

  • left and right point to child nodes
  • value contains the computed hash of children (cached after first calculation)

The isEmpty field indicates whether this is a "zero" node used for padding incomplete trees to maintain proper binary tree structure.

func EmptyLeaf

func EmptyLeaf() *Node

EmptyLeaf creates a leaf node containing 32 zero bytes, representing an empty or unset value in the Merkle tree.

func LeafFromBool

func LeafFromBool(b bool) *Node

LeafFromBool creates a 32-byte leaf node from a boolean value, encoded as 0x01 (true) or 0x00 (false) in the first byte with 31 bytes zero-padded.

func LeafFromBytes

func LeafFromBytes(b []byte) *Node

LeafFromBytes creates a 32-byte leaf node from a byte slice. If the slice is shorter than 32 bytes, it is right-padded with zeros. Panics if the slice exceeds 32 bytes.

func LeafFromUint8

func LeafFromUint8(i uint8) *Node

LeafFromUint8 creates a 32-byte leaf node from a uint8 value, stored in the first byte with the remaining 31 bytes zero-padded.

func LeafFromUint16

func LeafFromUint16(i uint16) *Node

LeafFromUint16 creates a 32-byte leaf node from a uint16 value, encoded as little-endian in the first 2 bytes with the remaining 30 bytes zero-padded.

func LeafFromUint32

func LeafFromUint32(i uint32) *Node

LeafFromUint32 creates a 32-byte leaf node from a uint32 value, encoded as little-endian in the first 4 bytes with the remaining 28 bytes zero-padded.

func LeafFromUint64

func LeafFromUint64(i uint64) *Node

LeafFromUint64 creates a 32-byte leaf node from a uint64 value, encoded as little-endian in the first 8 bytes with the remaining 24 bytes zero-padded.

func LeavesFromUint64

func LeavesFromUint64(items []uint64) []*Node

LeavesFromUint64 packs a slice of uint64 values into leaf nodes, with 4 values per 32-byte leaf (8 bytes each, little-endian). The final leaf is zero-padded if the number of items is not a multiple of 4.

func NewEmptyNode

func NewEmptyNode(zeroOrderHash []byte) *Node

NewEmptyNode creates an empty (zero-padding) tree node with the given precomputed zero-order hash. Empty nodes represent unused positions in the binary tree and are marked with isEmpty=true for efficient proof compression.

func NewNodeWithLR

func NewNodeWithLR(left, right *Node) *Node

NewNodeWithLR initializes a branch node.

func NewNodeWithValue

func NewNodeWithValue(value []byte) *Node

NewNodeWithValue initializes a leaf node.

func TreeFromChunks

func TreeFromChunks(chunks [][]byte) (*Node, error)

TreeFromChunks constructs a tree from leaf values. The number of leaves should be a power of 2.

func TreeFromNodes

func TreeFromNodes(leaves []*Node, limit int) (*Node, error)

TreeFromNodes constructs a tree from leaf nodes. This is useful for merging subtrees. The limit should be a power of 2. Adjacent sibling nodes will be filled with zero order hashes that have been precomputed based on the tree depth.

func TreeFromNodesProgressive

func TreeFromNodesProgressive(leaves []*Node) (*Node, error)

TreeFromNodesProgressive constructs a progressive tree from leaf nodes. This implements the progressive merkleization algorithm where chunks are split using base_size pattern (1, 4, 16, 64...) rather than even binary splits. Based on subtree_fill_progressive from remerkleable.

func TreeFromNodesProgressiveWithActiveFields

func TreeFromNodesProgressiveWithActiveFields(leaves []*Node, activeFields []byte) (*Node, error)

TreeFromNodesProgressiveWithActiveFields constructs a progressive tree with active fields bitvector. The progressive tree is created first, then mixed with the active fields.

func TreeFromNodesProgressiveWithMixin

func TreeFromNodesProgressiveWithMixin(leaves []*Node, num int) (*Node, error)

TreeFromNodesProgressiveWithMixin constructs a progressive tree with length mixin. The progressive tree is created first, then mixed with the length value.

func TreeFromNodesWithMixin

func TreeFromNodesWithMixin(leaves []*Node, num, limit int) (*Node, error)

TreeFromNodesWithMixin constructs a Merkle tree from leaves and mixes in the element count as a right sibling of the root. This is the standard SSZ merkleization for lists, where the tree root is hash(merkle_root || length). The limit is rounded up to the next power of two if not already one.

func (*Node) Get

func (n *Node) Get(index int) (*Node, error)

Get fetches a node with the given general index.

func (*Node) Hash

func (n *Node) Hash() []byte

Hash returns the hash of the subtree with the given Node as its root. If root has no children, it returns root's value (not its hash).

func (*Node) IsEmpty

func (n *Node) IsEmpty() bool

IsEmpty returns true if this node represents zero-padding.

func (*Node) IsLeaf

func (n *Node) IsLeaf() bool

IsLeaf returns true if this node has no children (is a leaf node).

func (*Node) Left

func (n *Node) Left() *Node

Left returns the left child node, or nil if this is a leaf.

func (*Node) Prove

func (n *Node) Prove(index int) (*Proof, error)

Prove returns a list of sibling values and hashes needed to compute the root hash for a given general index.

func (*Node) ProveMulti

func (n *Node) ProveMulti(indices []int) (*Multiproof, error)

ProveMulti generates a Multiproof for the given set of generalized indices. It collects the leaf values at each index and the minimal set of auxiliary hashes needed to reconstruct the root. Returns an error if any index cannot be found in the tree.

func (*Node) Right

func (n *Node) Right() *Node

Right returns the right child node, or nil if this is a leaf.

func (*Node) Show

func (n *Node) Show(maxDepth int)

Show displays the tree structure in a human-readable format for debugging.

This method prints the complete tree hierarchy starting from this node, showing generalized indices, hash values, and the tree structure. It's particularly useful for understanding how SSZ data maps to tree nodes and for debugging proof generation.

Parameters:

  • maxDepth: Maximum depth to display (0 for unlimited depth)

Output format:

  • INDEX: The generalized index of each node
  • HASH: 32-byte hash for branch nodes (computed from children)
  • VALUE: 32-byte data for leaf nodes (actual SSZ field data)
  • EMPTY: Indicates zero-padding nodes with their depth level
  • LEFT/RIGHT: Tree structure showing child relationships

Example output:

--- Show node ---
INDEX: 1
HASH: a1b2c3d4...
LEFT:
    INDEX: 2
    VALUE: e5f6g7h8...
RIGHT:
    INDEX: 3
    HASH: i9j0k1l2...
    LEFT:
        INDEX: 6
        VALUE: m3n4o5p6...
    RIGHT:
        INDEX: 7
        EMPTY: true (depth: 0)

func (*Node) Value

func (n *Node) Value() []byte

Value returns the raw 32-byte value stored in this node.

type Proof

type Proof struct {
	Index  int      // Generalized index of the proven leaf
	Leaf   []byte   // 32-byte leaf value
	Hashes [][]byte // Sibling hashes for verification path
}

Proof represents a Merkle proof for a single leaf against a generalized index.

A Merkle proof consists of the leaf value and a sequence of sibling hashes needed to reconstruct the path from the leaf to the root. The proof can be verified independently to confirm that the leaf value exists at the specified generalized index within the tree.

Fields:

  • Index: The generalized index of the leaf being proven
  • Leaf: The 32-byte value at the specified index
  • Hashes: Ordered sequence of sibling hashes for the path to root

type Wrapper

type Wrapper struct {
	// contains filtered or unexported fields
}

Wrapper implements the sszutils.HashWalker interface to construct a complete Merkle tree instead of computing a single hash. This allows generating proofs for any field within an SSZ structure by building the tree during the same traversal that would normally produce only the hash tree root.

Usage:

w := treeproof.NewWrapper()
// Use w as a HashWalker (e.g., via DynSsz.HashTreeRootWith or generated code)
myStruct.HashTreeRootWithDyn(specs, w)
tree := w.Node()
proof, _ := tree.Prove(generalizedIndex)

func NewWrapper

func NewWrapper() *Wrapper

NewWrapper creates a new Wrapper ready to construct a Merkle tree. The wrapper starts with an empty node list and pre-allocated scratch buffers.

func (*Wrapper) AddBytes

func (w *Wrapper) AddBytes(b []byte)

AddBytes adds a byte slice as a leaf node (<=32 bytes) or as a merkleized subtree of 32-byte chunks (>32 bytes).

func (*Wrapper) AddEmpty

func (w *Wrapper) AddEmpty()

AddEmpty adds an empty (all-zeros) leaf node to the wrapper's node list.

func (*Wrapper) AddNode

func (w *Wrapper) AddNode(n *Node)

AddNode appends a pre-constructed tree node to the wrapper's node list.

func (*Wrapper) AddUint8

func (w *Wrapper) AddUint8(i uint8)

AddUint8 adds a uint8 value as a single leaf node.

func (*Wrapper) AddUint16

func (w *Wrapper) AddUint16(i uint16)

AddUint16 adds a uint16 value as a single leaf node.

func (*Wrapper) AddUint32

func (w *Wrapper) AddUint32(i uint32)

AddUint32 adds a uint32 value as a single leaf node.

func (*Wrapper) AddUint64

func (w *Wrapper) AddUint64(i uint64)

AddUint64 adds a uint64 value as a single leaf node.

func (*Wrapper) Append

func (w *Wrapper) Append(i []byte)

Append appends raw bytes to the internal buffer. These bytes are converted to leaf nodes when a Merkleize method is called.

func (*Wrapper) AppendBool

func (w *Wrapper) AppendBool(b bool)

AppendBool appends a single-byte boolean (0x00 or 0x01) to the internal buffer.

func (*Wrapper) AppendBytes32

func (w *Wrapper) AppendBytes32(b []byte)

AppendBytes32 appends the given bytes to the internal buffer and pads with zeros to the next 32-byte boundary via FillUpTo32.

func (*Wrapper) AppendUint8

func (w *Wrapper) AppendUint8(i uint8)

AppendUint8 appends a single byte to the internal buffer.

func (*Wrapper) AppendUint16

func (w *Wrapper) AppendUint16(i uint16)

AppendUint16 appends a little-endian uint16 (2 bytes) to the internal buffer.

func (*Wrapper) AppendUint32

func (w *Wrapper) AppendUint32(i uint32)

AppendUint32 appends a little-endian uint32 (4 bytes) to the internal buffer.

func (*Wrapper) AppendUint64

func (w *Wrapper) AppendUint64(i uint64)

AppendUint64 appends a little-endian uint64 (8 bytes) to the internal buffer.

func (*Wrapper) Collapse added in v1.3.0

func (w *Wrapper) Collapse()

Collapse is a no-op for the tree proof wrapper.

func (*Wrapper) Commit

func (w *Wrapper) Commit(i int)

Commit constructs a binary Merkle tree from all nodes added since index i, replaces those nodes with the resulting subtree root, and adds it back to the node list.

func (*Wrapper) CommitProgressive

func (w *Wrapper) CommitProgressive(i int)

CommitProgressive creates a progressive tree from nodes

func (*Wrapper) CommitProgressiveWithActiveFields

func (w *Wrapper) CommitProgressiveWithActiveFields(i int, activeFields []byte)

CommitProgressiveWithActiveFields creates a progressive tree with active fields bitvector

func (*Wrapper) CommitProgressiveWithMixin

func (w *Wrapper) CommitProgressiveWithMixin(i, num int)

CommitProgressiveWithMixin creates a progressive tree with length mixin

func (*Wrapper) CommitWithMixin

func (w *Wrapper) CommitWithMixin(i, num, limit int)

CommitWithMixin constructs a binary Merkle tree from nodes since index i with a length mixin, used for SSZ list merkleization.

func (*Wrapper) CurrentIndex added in v1.3.0

func (w *Wrapper) CurrentIndex() int

CurrentIndex returns the current buffer index (debug only)

func (*Wrapper) FillUpTo32

func (w *Wrapper) FillUpTo32()

FillUpTo32 pads the internal buffer with zero bytes so its length is a multiple of 32. This ensures proper 32-byte chunk alignment for leaf nodes.

func (*Wrapper) Hash

func (w *Wrapper) Hash() []byte

Hash returns the 32-byte hash of the last node in the wrapper's node list.

func (*Wrapper) HashRoot

func (w *Wrapper) HashRoot() ([32]byte, error)

HashRoot returns the 32-byte hash tree root of the constructed tree. This implements the HashWalker interface's final step, producing the same root hash that a regular Hasher would compute.

func (*Wrapper) Index

func (w *Wrapper) Index() int

Index returns the current number of nodes in the wrapper's node list. This value is used as a checkpoint for subsequent Merkleize calls to identify which nodes should be combined into a subtree.

func (*Wrapper) Merkleize

func (w *Wrapper) Merkleize(indx int)

Merkleize flushes any buffered bytes as leaf nodes, then constructs a binary Merkle tree from all nodes added since the checkpoint index indx. The resulting subtree replaces those nodes as a single tree node.

func (*Wrapper) MerkleizeProgressive

func (w *Wrapper) MerkleizeProgressive(indx int)

MerkleizeProgressive flushes buffered bytes, then constructs a progressive Merkle tree from nodes since indx using the progressive split pattern (base sizes 1, 4, 16, 64...) instead of even binary splits.

func (*Wrapper) MerkleizeProgressiveWithActiveFields

func (w *Wrapper) MerkleizeProgressiveWithActiveFields(indx int, activeFields []byte)

MerkleizeProgressiveWithActiveFields flushes buffered bytes, constructs a progressive Merkle tree from nodes since indx, and mixes in the active fields bitvector as a right sibling. This is used for stable/progressive containers.

func (*Wrapper) MerkleizeProgressiveWithMixin

func (w *Wrapper) MerkleizeProgressiveWithMixin(indx int, num uint64)

MerkleizeProgressiveWithMixin flushes buffered bytes, constructs a progressive Merkle tree from nodes since indx, and mixes in the element count (num) as a right sibling.

func (*Wrapper) MerkleizeWithMixin

func (w *Wrapper) MerkleizeWithMixin(indx int, num, limit uint64)

MerkleizeWithMixin flushes buffered bytes, constructs a binary Merkle tree from nodes since indx, and mixes in the element count (num) as a right sibling. This implements SSZ list merkleization: hash(merkle_root || length).

func (*Wrapper) Node

func (w *Wrapper) Node() *Node

Node returns the single root node of the constructed Merkle tree. Panics if the wrapper does not contain exactly one node, which indicates incomplete merkleization.

func (*Wrapper) PutBitlist

func (w *Wrapper) PutBitlist(bb []byte, maxSize uint64)

PutBitlist parses a bitlist (with sentinel bit), creates leaf nodes from the data, and merkleizes them with a length mixin. The maxSize parameter determines the tree limit for proper padding.

func (*Wrapper) PutBool

func (w *Wrapper) PutBool(b bool)

PutBool adds a boolean value as a single 32-byte leaf node.

func (*Wrapper) PutBytes

func (w *Wrapper) PutBytes(b []byte)

PutBytes adds a byte slice as one or more 32-byte leaf nodes. If the slice exceeds 32 bytes, it is split into chunks and merkleized into a subtree.

func (*Wrapper) PutProgressiveBitlist

func (w *Wrapper) PutProgressiveBitlist(bb []byte)

PutProgressiveBitlist parses a bitlist (with sentinel bit), creates leaf nodes from the data, and merkleizes them using the progressive tree algorithm with a length mixin.

func (*Wrapper) PutUint8

func (w *Wrapper) PutUint8(i uint8)

PutUint8 adds a uint8 value as a single 32-byte leaf node.

func (*Wrapper) PutUint16

func (w *Wrapper) PutUint16(i uint16)

PutUint16 adds a uint16 value as a single 32-byte leaf node.

func (*Wrapper) PutUint32

func (w *Wrapper) PutUint32(i uint32)

PutUint32 adds a uint32 value as a single 32-byte leaf node.

func (*Wrapper) PutUint64

func (w *Wrapper) PutUint64(i uint64)

PutUint64 adds a uint64 value as a single 32-byte leaf node.

func (*Wrapper) PutUint64Array

func (w *Wrapper) PutUint64Array(b []uint64, maxCapacity ...uint64)

PutUint64Array appends all uint64 values as buffered bytes, pads to 32-byte alignment, and merkleizes. If maxCapacity is provided, the result is merkleized with a length mixin (list semantics); otherwise it uses fixed-size merkleization (vector semantics).

func (*Wrapper) StartTree added in v1.3.0

func (w *Wrapper) StartTree(_ sszutils.TreeType) int

StartTree opens a new SSZ object scope. For the tree proof wrapper, this is identical to Index() since the wrapper does not support incremental hashing.

func (*Wrapper) WithTemp

func (w *Wrapper) WithTemp(fn func(tmp []byte) []byte)

WithTemp provides a temporary scratch buffer to the given function, allowing callers to perform intermediate computations without additional allocations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL