Documentation
¶
Overview ¶
Package merklestrata provides stratified Merkle trees where groups of leaves share intermediate roots.
A standard Merkle tree hashes a flat list of leaves into one root. merklestrata adds structure: leaves are organized into named groups, each group gets its own Merkle subtree, and the group roots are combined into a top-level tree. This enables operations that flat trees cannot express efficiently:
- O(groups) diff: compare two trees by checking group roots, not individual leaves
- Absence proofs: prove a leaf does NOT exist using adjacent sorted leaves
- Scoped queries: compute a root for any subset of groups (cache keys, partial verification)
- Inclusion proofs: standard Merkle path from leaf through group root to tree root
Two Modes ¶
Tree provides a 2-level structure (root -> groups -> leaves):
f := merklestrata.Build(map[string][]merklestrata.Hash{
"users": {hash("user.Create"), hash("user.Delete")},
"billing": {hash("billing.Charge")},
})
proof, _ := f.Prove("users", hash("user.Create"))
merklestrata.Verify(proof, f.Root) // true
MultiLevel provides a 3-level tree (root -> groups -> subgroups -> leaves):
ml := merklestrata.BuildMultiLevel([]merklestrata.MultiLevelInput{
{Leaf: hash("e1"), Group: "pkg/auth", Subgroup: "calls"},
{Leaf: hash("e2"), Group: "pkg/auth", Subgroup: "imports"},
})
proof, _ := ml.Prove("pkg/auth", "calls", hash("e1"))
merklestrata.VerifyMultiLevel(proof, ml.Root) // true
Absence Proofs ¶
Prove that a leaf does NOT exist in a group. The proof includes the two adjacent sorted leaves that bracket the gap where the missing leaf would be inserted. Verifiers confirm both neighbors are in the tree and that they are adjacent (no room for the missing leaf between them):
absent, _ := f.ProveAbsent("users", hash("user.Ban"))
merklestrata.VerifyAbsent(absent, f.Root) // true (user.Ban is not in the tree)
Diff ¶
Compare two trees in O(groups) by checking intermediate roots:
added, removed, changed := merklestrata.Diff(oldTree, newTree)
Then drill into changed groups for leaf-level detail:
addedLeaves, removedLeaves := merklestrata.DiffLeaves(oldTree, newTree, "users")
Custom Domain Prefix ¶
Interior node hashes are computed as SHA-256(prefix || left || right). The default prefix is "merkle-strata\x00". Use WithPrefix to override for backward compatibility with existing Merkle tree implementations:
f := merklestrata.Build(groups, merklestrata.WithPrefix([]byte("myprefix\x00")))
Properties ¶
- Deterministic: same input set (any order) produces the same root
- Content-addressed: the root is a function of leaf content, not names or insertion order
- Immutable: Build returns a frozen structure; mutate inputs and rebuild
- Zero dependencies: uses only crypto/sha256, bytes, sort, fmt, strings from stdlib
- Proofs are self-contained: verifiable offline with just the proof bytes and a root hash
Index ¶
- func Diff(old, new *Tree) (added, removed, changed []string)
- func SortHashes(hashes []Hash)
- func Verify(proof *Proof, root Hash) bool
- func VerifyAbsent(proof *AbsenceProof, root Hash) bool
- func VerifyAbsentWithPrefix(proof *AbsenceProof, root Hash, prefix []byte) bool
- func VerifyMultiLevel(proof *MultiLevelProof, root Hash) bool
- func VerifyMultiLevelAbsent(proof *MultiLevelAbsenceProof, root Hash) bool
- func VerifyMultiLevelAbsentWithPrefix(proof *MultiLevelAbsenceProof, root Hash, prefix []byte) bool
- func VerifyMultiLevelWithPrefix(proof *MultiLevelProof, root Hash, prefix []byte) bool
- func VerifyWithPrefix(proof *Proof, root Hash, prefix []byte) bool
- type AbsenceProof
- type DiffOptions
- type DiffResult
- type Hash
- type HashFunc
- type MultiLevel
- type MultiLevelAbsenceProof
- type MultiLevelDiff
- type MultiLevelInput
- type MultiLevelProof
- type Option
- type Proof
- type Step
- type Tree
- func (f *Tree) GroupLeafCount(name string) int
- func (f *Tree) GroupRoot(name string) (Hash, bool)
- func (f *Tree) Groups() []string
- func (f *Tree) LeafCount() int
- func (f *Tree) Leaves(name string) []Hash
- func (f *Tree) Prove(groupName string, leaf Hash) (*Proof, error)
- func (f *Tree) ProveAbsent(groupName string, leaf Hash) (*AbsenceProof, error)
- func (f *Tree) SubRoot(groups []string) Hash
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Diff ¶
Diff compares two forests and returns which groups were added, removed, or changed. This is O(groups), not O(leaves): it compares intermediate roots without examining individual leaves.
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
old := forest.Build(map[string][][32]byte{
"users": {hash("user.Create")},
"auth": {hash("auth.Login")},
})
new := forest.Build(map[string][][32]byte{
"users": {hash("user.Create")}, // unchanged
"auth": {hash("auth.Login"), hash("auth.MFA")}, // changed
"logs": {hash("log.Write")}, // added
})
added, removed, changed := forest.Diff(old, new)
fmt.Printf("added: %v\n", added)
fmt.Printf("removed: %v\n", removed)
fmt.Printf("changed: %v\n", changed)
}
Output: added: [logs] removed: [] changed: [auth]
func SortHashes ¶
func SortHashes(hashes []Hash)
SortHashes sorts a slice of hashes lexicographically by bytes.Compare.
func Verify ¶
Verify checks that an inclusion proof is valid by recomputing the root. Uses the default domain prefix. For custom prefixes, use VerifyWithPrefix.
func VerifyAbsent ¶
func VerifyAbsent(proof *AbsenceProof, root Hash) bool
VerifyAbsent checks that an absence proof is valid. Uses the default domain prefix. For custom prefixes, use VerifyAbsentWithPrefix.
func VerifyAbsentWithPrefix ¶
func VerifyAbsentWithPrefix(proof *AbsenceProof, root Hash, prefix []byte) bool
VerifyAbsentWithPrefix checks an absence proof using a custom domain prefix.
func VerifyMultiLevel ¶
func VerifyMultiLevel(proof *MultiLevelProof, root Hash) bool
VerifyMultiLevel checks a 3-level proof by recomputing each level.
func VerifyMultiLevelAbsent ¶
func VerifyMultiLevelAbsent(proof *MultiLevelAbsenceProof, root Hash) bool
VerifyMultiLevelAbsent checks a 3-level absence proof.
func VerifyMultiLevelAbsentWithPrefix ¶
func VerifyMultiLevelAbsentWithPrefix(proof *MultiLevelAbsenceProof, root Hash, prefix []byte) bool
VerifyMultiLevelAbsentWithPrefix checks a 3-level absence proof with a custom prefix.
func VerifyMultiLevelWithPrefix ¶
func VerifyMultiLevelWithPrefix(proof *MultiLevelProof, root Hash, prefix []byte) bool
VerifyMultiLevelWithPrefix checks a 3-level proof with a custom prefix.
Types ¶
type AbsenceProof ¶
type AbsenceProof struct {
Missing Hash `json:"missing"`
Group string `json:"group"`
// Left is the largest leaf smaller than Missing (nil if Missing would be first).
Left *Hash `json:"left,omitempty"`
// Right is the smallest leaf larger than Missing (nil if Missing would be last).
Right *Hash `json:"right,omitempty"`
// LeftProof proves Left is in the tree.
LeftProof *Proof `json:"left_proof,omitempty"`
// RightProof proves Right is in the tree.
RightProof *Proof `json:"right_proof,omitempty"`
Root Hash `json:"root"`
}
AbsenceProof proves that a leaf does NOT exist in a group. It proves the two adjacent leaves that bracket the missing hash: left < missing < right. Since leaves are sorted, adjacency proves no room.
type DiffOptions ¶
type DiffOptions struct {
// Filter restricts the diff to the listed group names. When empty, all
// groups are compared.
Filter []string
// MaxChanges caps the total number of changed/added/removed groups reported.
// 0 means no cap.
MaxChanges int
}
DiffOptions controls diff behavior.
type DiffResult ¶
type DiffResult struct {
Added []string // groups in new but not old
Removed []string // groups in old but not new
Changed []string // groups in both but with different roots
Truncated bool // true if MaxChanges cap was reached
}
DiffResult contains the groups that differ between two trees.
func DiffWithOptions ¶
func DiffWithOptions(old, new *Tree, opts *DiffOptions) *DiffResult
DiffWithOptions compares two trees with optional filtering and cap.
type Hash ¶
type Hash = [32]byte
Hash is a 32-byte SHA-256 hash.
func DiffLeaves ¶
DiffLeaves compares the leaves of a specific group between two trees. Returns added and removed leaf hashes. This is useful after Diff identifies a changed group and you want to know which specific leaves differ.
type HashFunc ¶
HashFunc is a function that returns a new hash.Hash instance. Used to configure the hash algorithm for tree construction.
type MultiLevel ¶
type MultiLevel struct {
// Root is the top-level Merkle root.
Root Hash
// GroupRoots maps group name to its intermediate root.
GroupRoots map[string]Hash
// SubgroupRoots maps "group:subgroup" to its root.
SubgroupRoots map[string]Hash
// GroupLeafCounts tracks leaf count per group.
GroupLeafCounts map[string]int
// TotalLeaves is the total leaf count.
TotalLeaves int
// contains filtered or unexported fields
}
MultiLevel is a 3-level stratified Merkle tree: root -> groups -> subgroups -> leaves. This models hierarchies like: repo -> packages -> edge-types -> edges.
func BuildMultiLevel ¶
func BuildMultiLevel(inputs []MultiLevelInput, opts ...Option) *MultiLevel
BuildMultiLevel constructs a 3-level tree from inputs.
Structure:
root = merkle(sorted group roots)
group root = merkle(sorted subgroup roots for this group)
subgroup root = merkle(sorted leaf hashes in this subgroup)
leaf
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
ml := forest.BuildMultiLevel([]forest.MultiLevelInput{
{Leaf: hash("e1"), Group: "pkg/auth", Subgroup: "calls"},
{Leaf: hash("e2"), Group: "pkg/auth", Subgroup: "imports"},
{Leaf: hash("e3"), Group: "pkg/store", Subgroup: "calls"},
})
proof, err := ml.Prove("pkg/auth", "calls", hash("e1"))
if err != nil {
panic(err)
}
valid := forest.VerifyMultiLevel(proof, ml.Root)
fmt.Printf("3-level proof valid: %v\n", valid)
fmt.Printf("groups: %d, leaves: %d\n", len(ml.GroupRoots), ml.TotalLeaves)
}
Output: 3-level proof valid: true groups: 2, leaves: 3
func (*MultiLevel) Prove ¶
func (ml *MultiLevel) Prove(group, subgroup string, leaf Hash) (*MultiLevelProof, error)
Prove generates a 3-level inclusion proof.
func (*MultiLevel) ProveAbsent ¶
func (ml *MultiLevel) ProveAbsent(group, subgroup string, leaf Hash) (*MultiLevelAbsenceProof, error)
ProveAbsent generates a 3-level absence proof that leaf does NOT exist in the given group and subgroup.
func (*MultiLevel) SubgraphRoot ¶
func (ml *MultiLevel) SubgraphRoot(groups []string) Hash
SubgraphRoot computes a root for a subset of groups (not subgroups). This is the cache key for "did anything in these groups change?"
type MultiLevelAbsenceProof ¶
type MultiLevelAbsenceProof struct {
Missing Hash `json:"missing"`
Group string `json:"group"`
Subgroup string `json:"subgroup"`
Left *Hash `json:"left,omitempty"`
Right *Hash `json:"right,omitempty"`
LeftProof *MultiLevelProof `json:"left_proof,omitempty"`
RightProof *MultiLevelProof `json:"right_proof,omitempty"`
Root Hash `json:"root"`
}
MultiLevelAbsenceProof proves a leaf does NOT exist in a subgroup.
type MultiLevelDiff ¶
type MultiLevelDiff struct {
ChangedGroups []string // groups whose root changed
AddedGroups []string // groups in new but not old
RemovedGroups []string // groups in old but not new
ChangedSubgroups []string // "group:subgroup" keys whose root changed
RootChanged bool
}
DiffMultiLevel compares two multi-level trees.
func DiffMultiLevelTrees ¶
func DiffMultiLevelTrees(old, new *MultiLevel) *MultiLevelDiff
DiffMultiLevel compares two multi-level trees at each level.
type MultiLevelInput ¶
type MultiLevelInput struct {
Leaf Hash
Group string // e.g. package path
Subgroup string // e.g. edge type
}
MultiLevelInput is a leaf with its group and subgroup metadata.
type MultiLevelProof ¶
type MultiLevelProof struct {
Leaf Hash `json:"leaf"`
Group string `json:"group"`
Subgroup string `json:"subgroup"`
LeafPath []Step `json:"leaf_path"` // leaf -> subgroup root
SubgroupRoot Hash `json:"subgroup_root"`
SubgroupPath []Step `json:"subgroup_path"` // subgroup root -> group root
GroupRoot Hash `json:"group_root"`
GroupPath []Step `json:"group_path"` // group root -> top root
Root Hash `json:"root"`
}
MultiLevelProof is a 3-level proof: leaf -> subgroup root -> group root -> root.
type Option ¶
type Option func(*options)
Option configures tree construction.
func WithHash ¶
WithHash sets a custom hash function for tree construction. The function must return a hash.Hash that produces 32-byte digests. Default is crypto/sha256. Use this for BLAKE3, SHA-512/256, or other 32-byte hash functions.
func WithPrefix ¶
WithPrefix sets a custom domain prefix for interior node hashes. Use this for backward compatibility when migrating from an existing Merkle tree implementation that uses a different prefix.
type Proof ¶
type Proof struct {
Leaf Hash `json:"leaf"`
Group string `json:"group"`
LeafPath []Step `json:"leaf_path"` // leaf -> group root
GroupRoot Hash `json:"group_root"`
GroupPath []Step `json:"group_path"` // group root -> tree root
Root Hash `json:"root"`
}
Proof is an inclusion proof that a leaf exists in a group within the tree. Verification recomputes: leaf -> group root -> tree root.
type Step ¶
type Step struct {
Sibling Hash `json:"sibling"`
IsLeft bool `json:"is_left"` // true if sibling is on the left
}
Step represents one step in a Merkle proof path.
type Tree ¶ added in v0.3.0
type Tree struct {
// Root is the top-level Merkle root covering all groups.
Root Hash
// contains filtered or unexported fields
}
Tree is an immutable stratified Merkle tree built from grouped leaves.
func Build ¶
Build constructs a tree from grouped leaves. Leaves within each group are sorted lexicographically by bytes.Compare. Groups are combined into a top-level Merkle tree sorted by group root hash.
Options can override the domain prefix (default: "merkle-strata\0").
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
f := forest.Build(map[string][][32]byte{
"users": {hash("user.Create"), hash("user.Delete")},
"billing": {hash("billing.Charge"), hash("billing.Refund")},
})
fmt.Printf("non-zero root: %v\n", f.Root != [32]byte{})
fmt.Printf("groups: %v\n", f.Groups())
}
Output: non-zero root: true groups: [billing users]
func (*Tree) GroupLeafCount ¶ added in v0.3.0
GroupLeafCount returns the number of leaves in a specific group. Returns 0 if the group does not exist.
func (*Tree) GroupRoot ¶ added in v0.3.0
GroupRoot returns the intermediate Merkle root for a single group. Returns false if the group does not exist.
func (*Tree) LeafCount ¶ added in v0.3.0
LeafCount returns the total number of leaves across all groups.
func (*Tree) Leaves ¶ added in v0.3.0
Leaves returns the sorted leaf hashes for a group. Returns nil if the group does not exist.
func (*Tree) Prove ¶ added in v0.3.0
Prove generates an inclusion proof that leaf exists in the named group.
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
f := forest.Build(map[string][][32]byte{
"auth": {hash("auth.Login"), hash("auth.Logout"), hash("auth.Refresh")},
})
proof, err := f.Prove("auth", hash("auth.Login"))
if err != nil {
panic(err)
}
valid := forest.Verify(proof, f.Root)
fmt.Printf("valid: %v\n", valid)
}
Output: valid: true
func (*Tree) ProveAbsent ¶ added in v0.3.0
func (f *Tree) ProveAbsent(groupName string, leaf Hash) (*AbsenceProof, error)
ProveAbsent generates an absence proof that leaf does NOT exist in the named group. Returns an error if the leaf IS found (can't prove absence of something present).
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
f := forest.Build(map[string][][32]byte{
"auth": {hash("auth.Login"), hash("auth.Logout")},
})
absent, err := f.ProveAbsent("auth", hash("auth.Revoke"))
if err != nil {
panic(err)
}
valid := forest.VerifyAbsent(absent, f.Root)
fmt.Printf("absent verified: %v\n", valid)
}
Output: absent verified: true
func (*Tree) SubRoot ¶ added in v0.3.0
SubRoot computes a Merkle root for a subset of groups. Useful for scoped cache keys: "did anything in these groups change?" Returns the empty hash if none of the groups exist.
Example ¶
package main
import (
"crypto/sha256"
"fmt"
forest "github.com/blackwell-systems/merkle-strata"
)
func hash(s string) [32]byte {
return sha256.Sum256([]byte(s))
}
func main() {
f := forest.Build(map[string][][32]byte{
"users": {hash("user.Create")},
"billing": {hash("billing.Charge")},
"auth": {hash("auth.Login")},
})
// Cache key for just users + auth (ignores billing changes).
sub := f.SubRoot([]string{"users", "auth"})
fmt.Printf("sub root (non-zero): %v\n", sub != [32]byte{})
}
Output: sub root (non-zero): true