merklestrata

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 6 Imported by: 0

README

merkle-strata

Blackwell Systems Go Reference License Tests


Standard Merkle trees are flat. Real data has structure.

merkle-strata builds stratified Merkle trees where groups of leaves share intermediate roots. Prove something exists. Prove something doesn't. Detect which groups changed without scanning leaves. Query any subtree without loading the rest.

import strata "github.com/blackwell-systems/merkle-strata"

Zero dependencies. Stdlib only. go get github.com/blackwell-systems/merkle-strata


In 30 Seconds

f := strata.Build(map[string][][32]byte{
    "users":   {hash("user.Create"), hash("user.Delete")},
    "billing": {hash("billing.Charge"), hash("billing.Refund")},
    "auth":    {hash("auth.Login"), hash("auth.Logout")},
})

// One root covers everything.
fmt.Printf("root: %x\n", f.Root)

// Prove a leaf exists (offline-verifiable).
proof, _ := f.Prove("auth", hash("auth.Login"))
strata.Verify(proof, f.Root) // true

// Prove a leaf does NOT exist.
absent, _ := f.ProveAbsent("auth", hash("auth.Revoke"))
strata.VerifyAbsent(absent, f.Root) // true

// Which groups changed?
added, removed, changed := strata.Diff(oldTree, newTree)

// Cache key for a subset of groups.
subRoot := f.SubRoot([]string{"users", "auth"})

Two Modes

Tree (2-level: root -> groups -> leaves)

For simple grouped data. One level of structure.

f := strata.Build(groups)
f.Prove("group", leaf)
f.ProveAbsent("group", leaf)
f.SubRoot([]string{"group1", "group2"})
strata.Diff(old, new)

MultiLevel (3-level: root -> groups -> subgroups -> leaves)

For hierarchical data. Two levels of structure (e.g. packages containing edge types containing edges).

ml := strata.BuildMultiLevel([]strata.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"},
})

// 3-level proof: leaf -> subgroup root -> group root -> top root.
proof, _ := ml.Prove("pkg/auth", "calls", hash("e1"))
strata.VerifyMultiLevel(proof, ml.Root)

// Which groups changed? Which subgroups within them?
diff := strata.DiffMultiLevelTrees(oldML, newML)
// diff.ChangedGroups: ["pkg/store"]
// diff.ChangedSubgroups: ["pkg/store:calls"]

// Cache key for a subset of groups.
subRoot := ml.SubgraphRoot([]string{"pkg/auth"})

Why This Exists

Library Structure Absence proofs Group diff Scoped queries
cbergoon/merkletree Flat No No No
txaty/go-merkletree Flat (parallel) No No No
celestiaorg/nmt Namespaced (1 level) No (range) No No
celestiaorg/smt Sparse (key-indexed) Yes (empty leaf) No No
merkle-strata Stratified (2 or 3 level) Yes (gap proof) Yes (O(groups)) Yes

NMT groups by a single namespace. SMT gives absence by construction but uses fixed-depth key-space indexing. merkle-strata groups by arbitrary string keys, supports variable leaf counts per group, and proves absence via sorted adjacency without empty-leaf overhead.


Full API

Building

// 2-level: groups of leaves.
func Build(groups map[string][]Hash, opts ...Option) *Tree

// 3-level: groups of subgroups of leaves.
func BuildMultiLevel(inputs []MultiLevelInput, opts ...Option) *MultiLevel

// Custom domain prefix (for backward compat with existing systems).
strata.Build(groups, strata.WithPrefix([]byte("merkle\x00")))

Inclusion Proofs

// 2-level.
func (f *Tree) Prove(group string, leaf Hash) (*Proof, error)
func Verify(proof *Proof, root Hash) bool
func VerifyWithPrefix(proof *Proof, root Hash, prefix []byte) bool

// 3-level.
func (ml *MultiLevel) Prove(group, subgroup string, leaf Hash) (*MultiLevelProof, error)
func VerifyMultiLevel(proof *MultiLevelProof, root Hash) bool
func VerifyMultiLevelWithPrefix(proof *MultiLevelProof, root Hash, prefix []byte) bool

Absence Proofs

func (f *Tree) ProveAbsent(group string, leaf Hash) (*AbsenceProof, error)
func VerifyAbsent(proof *AbsenceProof, root Hash) bool
func VerifyAbsentWithPrefix(proof *AbsenceProof, root Hash, prefix []byte) bool

Queries

// Scoped root for cache keys.
func (f *Tree) SubRoot(groups []string) Hash
func (ml *MultiLevel) SubgraphRoot(groups []string) Hash

// Inspect structure.
func (f *Tree) GroupRoot(name string) (Hash, bool)
func (f *Tree) Groups() []string
func (f *Tree) Leaves(group string) []Hash
func (f *Tree) LeafCount() int
func (f *Tree) GroupLeafCount(name string) int

Diff

// Simple diff.
func Diff(old, new *Tree) (added, removed, changed []string)

// With filtering and cap.
func DiffWithOptions(old, new *Tree, opts *DiffOptions) *DiffResult

// Leaf-level diff within a single group.
func DiffLeaves(old, new *Tree, group string) (added, removed []Hash)

// 3-level diff.
func DiffMultiLevelTrees(old, new *MultiLevel) *MultiLevelDiff

Utilities

func SortHashes(hashes []Hash)

Performance

Operation Complexity Notes
Build O(N log N) N = total leaves. Sort per group + tree construction.
Prove O(log G + log L) G = groups, L = leaves in target group.
Verify O(log G + log L) Hash recomputation only. No tree needed.
Diff O(G) Compares group roots. Skips unchanged groups entirely.
SubRoot O(S log S) S = subset size.
DiffLeaves O(L) For one changed group after Diff identifies it.

Proof Format

Proofs are self-contained, JSON-serializable, and verifiable offline (no database or tree needed, just the proof bytes and a root hash):

type Proof struct {
    Leaf      [32]byte `json:"leaf"`
    Group     string   `json:"group"`
    LeafPath  []Step   `json:"leaf_path"`   // leaf -> group root
    GroupRoot [32]byte `json:"group_root"`
    GroupPath []Step   `json:"group_path"`  // group root -> tree root
    Root      [32]byte `json:"root"`
}

type MultiLevelProof struct {
    Leaf         [32]byte `json:"leaf"`
    Group        string   `json:"group"`
    Subgroup     string   `json:"subgroup"`
    LeafPath     []Step   `json:"leaf_path"`     // leaf -> subgroup root
    SubgroupRoot [32]byte `json:"subgroup_root"`
    SubgroupPath []Step   `json:"subgroup_path"` // subgroup root -> group root
    GroupRoot    [32]byte `json:"group_root"`
    GroupPath    []Step   `json:"group_path"`    // group root -> top root
    Root         [32]byte `json:"root"`
}

Absence proofs include the two adjacent sorted leaves that bracket the gap, plus inclusion proofs for both neighbors. Verifiers confirm: left < missing < right, and both neighbors are in the tree.


Design Choices

Sorted leaves. Leaves within a group are sorted by bytes.Compare. Same set = same root regardless of insertion order. Enables absence proofs via binary search for gaps.

Domain-prefixed interior nodes. SHA-256("merkle-strata\0" || left || right) prevents second-preimage attacks and cross-tree collisions. Configurable via WithPrefix for systems with existing hash schemes.

Immutable. Build returns a frozen structure. Rebuild on mutation. Trees are cheap to construct (microseconds for hundreds of groups).

No dependencies. crypto/sha256, bytes, sort, fmt, strings. Nothing else.


Use Cases

Code intelligence. Group edges by package, prove a relationship exists at a snapshot, detect which packages changed. (This is what knowing uses it for.)

Audit trails. Prove an event occurred (or didn't) in a log partitioned by category. Offline verification without database access.

Configuration management. Group settings by service. SubRoot(["api", "worker"]) gives a single hash that changes only when those services' configs change.

Supply chain. Group dependencies by source. Prove a specific version was or wasn't included at a point in time.

Multi-tenant storage. Group records by tenant. Per-tenant roots enable isolated verification without exposing other tenants' data.


License

MIT

The Go gopher was originally designed by Renee French and is licensed under Creative Commons Attribution 4.0.

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Diff

func Diff(old, new *Tree) (added, removed, changed []string)

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

func Verify(proof *Proof, root Hash) bool

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.

func VerifyWithPrefix

func VerifyWithPrefix(proof *Proof, root Hash, prefix []byte) bool

VerifyWithPrefix checks an inclusion proof using a custom domain 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

func DiffLeaves(old, new *Tree, group string) (added, removed []Hash)

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

type HashFunc func() hash.Hash

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

func WithHash(fn HashFunc) Option

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

func WithPrefix(prefix []byte) Option

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

func Build(groups map[string][]Hash, opts ...Option) *Tree

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

func (f *Tree) GroupLeafCount(name string) int

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

func (f *Tree) GroupRoot(name string) (Hash, bool)

GroupRoot returns the intermediate Merkle root for a single group. Returns false if the group does not exist.

func (*Tree) Groups added in v0.3.0

func (f *Tree) Groups() []string

Groups returns the list of group names in the tree.

func (*Tree) LeafCount added in v0.3.0

func (f *Tree) LeafCount() int

LeafCount returns the total number of leaves across all groups.

func (*Tree) Leaves added in v0.3.0

func (f *Tree) Leaves(name string) []Hash

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

func (f *Tree) Prove(groupName string, leaf Hash) (*Proof, error)

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

func (f *Tree) SubRoot(groups []string) Hash

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

Jump to

Keyboard shortcuts

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