Skip to content
Permalink
Browse files

support unbalanced trees (#5)

  • Loading branch information...
noamnelke committed Mar 18, 2019
1 parent c53c875 commit 1a334a4997301c9db11b74d1bf8811f54a2329c1
Showing with 785 additions and 111 deletions.
  1. +3 −0 .travis.yml
  2. +0 −9 iterators.go
  3. +100 −21 merkle.go
  4. +130 −20 merkle_test.go
  5. +19 −14 proving.go
  6. +136 −21 proving_test.go
  7. +57 −10 treecache.go
  8. +212 −0 treecache_test.go
  9. +128 −16 validation_test.go
@@ -0,0 +1,3 @@
language: go
go: 1.11.x
env: GO111MODULE=on
@@ -8,15 +8,6 @@ type positionsIterator struct {
s []uint64
}

func (it *positionsIterator) next() (pos position, found bool) {
if len(it.s) == 0 {
return position{}, false
}
index := it.s[0]
it.s = it.s[1:]
return position{index: index}, true
}

func (it *positionsIterator) peek() (pos position, found bool) {
if len(it.s) == 0 {
return position{}, false
121 merkle.go
@@ -7,14 +7,25 @@ import (
"sort"
)

var ErrorIncompleteTree = errors.New("number of leaves must be a power of 2")
var emptyNode node

// PaddingValue is used for padding unbalanced trees. This value should not be permitted at the leaf layer to
// distinguish padding from actual members of the tree.
var PaddingValue = node{
value: make([]byte, NodeSize), // Zero filled.
onProvenPath: false,
}

// node is a node in the merkle tree.
type node struct {
value []byte
onProvenPath bool // Whether this node is an ancestor of a leaf whose membership in the tree is being proven.
}

func (n node) IsEmpty() bool {
return n.value == nil
}

// layer is a layer in the merkle tree.
type layer struct {
height uint
@@ -26,7 +37,7 @@ type layer struct {
// ensureNextLayerExists creates the next layer if it doesn't exist.
func (l *layer) ensureNextLayerExists(cache map[uint]io.Writer) {
if l.next == nil {
l.next = newLayer(l.height+1, cache[(l.height+1)])
l.next = newLayer(l.height+1, cache[(l.height + 1)])
}
}

@@ -71,6 +82,7 @@ type Tree struct {
proof [][]byte
leavesToProve *sparseBoolStack
cache map[uint]io.Writer
minHeight uint
}

// AddLeaf incorporates a new leaf to the state of the tree. It updates the state required to eventually determine the
@@ -96,7 +108,7 @@ func (t *Tree) AddLeaf(value []byte) error {

// If no node is pending, then this node is a left sibling,
// pending for its right sibling before its parent can be calculated.
if l.parking.value == nil {
if l.parking.IsEmpty() {
l.parking = n
break
} else {
@@ -124,30 +136,90 @@ func (t *Tree) AddLeaf(value []byte) error {
return lastCachingError
}

// Root returns the root of the tree or an error if the number of leaves added is not a power of 2.
func (t *Tree) Root() ([]byte, error) {
func nextOrEmptyLayer(l *layer) *layer {
if l.next != nil {
return l.next
}
return &layer{height: l.height + 1}
}

// Root returns the root of the tree.
// If the tree is unbalanced (num. of leaves is not a power of 2) it will perform padding on-the-fly.
func (t *Tree) Root() []byte {
root, _ := t.RootAndProof()
return root
}

// Proof returns a partial tree proving the membership of leaves that were passed in leavesToProve when the tree was
// initialized. For a single proved leaf this is a standard merkle proof (one sibling per layer of the tree from the
// leaves to the root, excluding the proved leaf and root).
// If the tree is unbalanced (num. of leaves is not a power of 2) it will perform padding on-the-fly.
func (t *Tree) Proof() [][]byte {
_, proof := t.RootAndProof()
return proof
}

// RootAndProof returns the root of the tree and a partial tree proving the membership of leaves that were passed in
// leavesToProve when the tree was initialized. For a single proved leaf this is a standard merkle proof (one sibling
// per layer of the tree from the leaves to the root, excluding the proved leaf and root).
// If the tree is unbalanced (num. of leaves is not a power of 2) it will perform padding on-the-fly.
func (t *Tree) RootAndProof() ([]byte, [][]byte) {
ephemeralProof := t.proof
var ephemeralNode node
l := t.baseLayer
for {
if l.next == nil {
return l.parking.value, nil
for height := uint(0); height < t.minHeight || l != nil; height++ {

// If we've reached the last layer and the ephemeral node is still empty, the tree is balanced and the parked
// node is its root.
// In any other case (minHeight not reached, or the tree is unbalanced) we want to add padding at this point.
reachedMinHeight := height >= t.minHeight
onLastLayer := l != nil && l.next == nil
parkingIsBalancedTreeRoot := reachedMinHeight && onLastLayer && ephemeralNode.IsEmpty()
if parkingIsBalancedTreeRoot {
return l.parking.value, ephemeralProof
}
if l.parking.value != nil {
return nil, ErrorIncompleteTree

var parking node
if l != nil {
parking = l.parking
}
parent, lChild, rChild := t.calcEphemeralParent(parking, ephemeralNode)

// Consider adding children to the ephemeralProof. `onProvenPath` must be explicitly set -- an empty node has
// the default value `false` and would never pass this point.
if parent.onProvenPath {
if !lChild.onProvenPath {
ephemeralProof = append(ephemeralProof, lChild.value)
}
if !rChild.onProvenPath {
ephemeralProof = append(ephemeralProof, rChild.value)
}
}
ephemeralNode = parent
if l != nil {
l = l.next
}
l = l.next
}
return ephemeralNode.value, ephemeralProof
}

// Proof returns a partial tree proving the membership of leaves that were passed in leavesToProve when the tree was
// initialized or an error if the number of leaves added is not a power of 2. For a single proved leaf this is a
// standard merkle proof (one sibling per layer of the tree from the leaves to the root, excluding the proved leaf
// and root).
func (t *Tree) Proof() ([][]byte, error) {
// We call t.Root() to traverse the layers and ensure the tree is full.
if _, err := t.Root(); err != nil {
return nil, err
// calcEphemeralParent calculates the parent using the layer parking and ephemeralNode. When one of those is missing it
// uses PaddingValue to pad. It returns the actual nodes used along with the parent.
func (t *Tree) calcEphemeralParent(parking, ephemeralNode node) (parent, lChild, rChild node) {
switch {
case !parking.IsEmpty() && !ephemeralNode.IsEmpty():
lChild, rChild = parking, ephemeralNode

case !parking.IsEmpty() && ephemeralNode.IsEmpty():
lChild, rChild = parking, PaddingValue

case parking.IsEmpty() && !ephemeralNode.IsEmpty():
lChild, rChild = ephemeralNode, PaddingValue

default: // both are empty
return emptyNode, emptyNode, emptyNode
}
return t.proof, nil
return t.calcParent(lChild, rChild), lChild, rChild
}

// calcParent returns the parent node of two child nodes.
@@ -162,9 +234,10 @@ type TreeBuilder struct {
hash HashFunc
leavesToProves []uint64
cache map[uint]io.Writer
minHeight uint
}

func NewTreeBuilder(hash func(lChild []byte, rChild []byte) []byte) TreeBuilder {
func NewTreeBuilder(hash HashFunc) TreeBuilder {
return TreeBuilder{hash: hash}
}

@@ -177,6 +250,7 @@ func (tb TreeBuilder) Build() *Tree {
hash: tb.hash,
leavesToProve: newSparseBoolStack(tb.leavesToProves),
cache: tb.cache,
minHeight: tb.minHeight,
}
}

@@ -190,6 +264,11 @@ func (tb TreeBuilder) WithCache(cache map[uint]io.Writer) TreeBuilder {
return tb
}

func (tb TreeBuilder) WithMinHeight(minHeight uint) TreeBuilder {
tb.minHeight = minHeight
return tb
}

func NewTree(hash HashFunc) *Tree {
return NewTreeBuilder(hash).Build()
}
Oops, something went wrong.

0 comments on commit 1a334a4

Please sign in to comment.
You can’t perform that action at this time.