-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtree.go
More file actions
157 lines (138 loc) · 5.2 KB
/
Copy pathtree.go
File metadata and controls
157 lines (138 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package merkle
import (
"math/bits"
)
// Tree represents a Merkle tree.
type Tree struct {
hasher Hasher
leafHasher LeafHasher
buf []byte // Buffer for temporary storage of hashes
leafBuf []byte // Buffer for temporary storage of leaf hashes
padding []byte // Padding for the tree
minHeight uint64 // Minimum height of the tree
leavesToProve []uint64 // leavesToProve is sorted set of indices of leaves to prove
parkedNodes [][]byte // The parked nodes of the tree
onProvingPath []bool // Indicates if the parked nodes are on the proving path
currentLeaf uint64 // The current leaf index
proof [][]byte // The proof of the leaves to prove
}
// NodeSize returns the length of the hash used for the nodes in the tree.
func (t *Tree) NodeSize() int {
return t.hasher.Size()
}
// Add adds a new value (leaf) to the tree.
//
// Call this method for each leaf you want to add to the tree before retrieving the root hash with Root() or
// RootAndProof().
func (t *Tree) Add(value []byte) {
curNode := t.leafHasher.Hash(t.leafBuf, value, t.parkedNodes)
// If needed, check if the current leaf is on the proving path
curOnProvingPath := false
if len(t.leavesToProve) > 0 && t.currentLeaf == t.leavesToProve[0] {
curOnProvingPath = true
t.leavesToProve = t.leavesToProve[1:]
}
t.currentLeaf++
// Loop through the layers (parked nodes) of the tree
for height := 0; ; height++ {
// If there is no layer at current height, add one
if height == len(t.parkedNodes) {
t.parkedNodes = append(t.parkedNodes, nil)
t.onProvingPath = append(t.onProvingPath, false)
}
parkingNode := &t.parkedNodes[height]
parkingOnProvingPath := &t.onProvingPath[height]
// If no node is parking, then the current node is a left sibling
// add it as the parking node and keep information on it being on the proving path or not
if *parkingNode == nil {
*parkingNode = append((*parkingNode)[:0], curNode...)
*parkingOnProvingPath = curOnProvingPath
break
}
// If the parking node is not nil, then the current node is a right sibling
switch {
case *parkingOnProvingPath && !curOnProvingPath:
// add the right child (current node) to the proof
proofNode := make([]byte, len(curNode))
copy(proofNode, curNode)
t.proof = append(t.proof, proofNode)
case !*parkingOnProvingPath && curOnProvingPath:
// add the left child (parking node) to the proof
proofNode := make([]byte, len(*parkingNode))
copy(proofNode, *parkingNode)
t.proof = append(t.proof, proofNode)
default:
// either both or none are on the proving path
// do not add anything to the proof
}
// Hash the parking node (left child) and the current node (right child) together
// store the result in the current node and move to the next layer
root := t.hasher.Hash(t.buf, *parkingNode, curNode)
curNode = append(curNode[:0], root...)
curOnProvingPath = *parkingOnProvingPath || curOnProvingPath
*parkingNode = nil
*parkingOnProvingPath = false
}
}
// Root returns the root hash of the tree.
func (t *Tree) Root() []byte {
root, _ := t.RootAndProof()
return root
}
// RootAndProof returns the root hash and the proof for the leaves to prove.
func (t *Tree) RootAndProof() ([]byte, [][]byte) {
proof := t.makeProof()
var root []byte
onProvingPath := false
for height, parkedNode := range t.parkedNodes {
// If this is a balanced tree, the parking node is the root and the proof is complete
if parkedNode != nil && root == nil && height == len(t.parkedNodes)-1 {
root = append(root[:0], parkedNode...) // Copy the parking node to the root
break
}
// Otherwise check if we are on the proving path and need to add one of the nodes to the proof
switch {
case t.onProvingPath[height] && !onProvingPath:
proofNode := make([]byte, t.hasher.Size())
copy(proofNode, root)
proof = append(proof, proofNode)
onProvingPath = true
case onProvingPath && !t.onProvingPath[height]:
proofNode := make([]byte, t.hasher.Size())
copy(proofNode, parkedNode)
proof = append(proof, proofNode)
default:
// either both or none are on the proving path, do not add anything to the proof
}
// In unbalanced trees walk up the layers by hashing the current root and parking node and use as new root
// If either is nil, use the padding value instead
// If both are nil continue with next layer
switch {
case parkedNode != nil && root != nil:
root = t.hasher.Hash(root, parkedNode, root)
case parkedNode != nil:
root = t.hasher.Hash(root, parkedNode, t.padding)
case root != nil:
root = t.hasher.Hash(root, root, t.padding)
}
}
// If the height is less than the minimum height, add padding nodes
for i := uint64(len(t.parkedNodes)); i < t.minHeight; i++ {
root = t.hasher.Hash(root, root, t.padding)
proof = append(proof, t.padding)
}
return root, proof
}
// makeProof allocates a proof object with a size that fits the requested proof without reallocating while building it.
func (t *Tree) makeProof() [][]byte {
if t.leavesToProve == nil {
return nil
}
proofLen := max(int(t.minHeight), bits.Len64(t.currentLeaf)-1, len(t.proof))
proof := make([][]byte, len(t.proof), proofLen)
for i, p := range t.proof {
proof[i] = make([]byte, len(p))
copy(proof[i], p)
}
return proof
}