-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtree.go
295 lines (268 loc) · 8.97 KB
/
tree.go
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package tree
import (
"encoding/binary"
"fmt"
"github.com/cyphrme/coze"
)
// See tree/README.md documentation.
//
// Recursive options are only populated if the currently branch level is not
// leaves and contains a deeper level.
//
// # Structure Variables
//
// Alg: Hashing algorithm used for creating the tree.
//
// Seed: "Private" seed. Must be same size as digest.
//
// ID: "Public" seed identifier. The digest of the seed is the identifier, and
// is not apart of Branch.
//
// BLS: "Branch Level Sizes" Size for each level of a recursive tree. Populate
// assumes a symmetrical tree. The last level may be non-symmetrical if
// MaxTotalLeaves is set. The hypothetical "Branch level" variable, which would
// be an integer representing how deep a branch is, is not stored as an explicit
// variable but is known by the parent from the values of BLS. A "Branch"
// variable would be just the seed of a branch as named by the parent.
//
// Branches: The digest values of the branches based on root seed for this
// level. Each branch value is the seed for the respective children. At the
// edge of the tree, Branches are termed "leaves" and have no further children
// calculated. Branches does not include children branches.
//
// Skip: Optional starting position for the first level. Populates tree
// starting at this position.
//
// PathCalc: If true, Paths, PathsID, Leaves, and LeavesID are calculated.
//
// Paths: ("Private") The tree path (all branches above) for all nodes. All
// leaves in a branch share the same Path. Both keys and values are seeds.
// Object of keys as string, values as []B64 of path, e.g. "paths": {"77Jo...":
// ["0RJw..."],
//
// PathsID: ("Public") Like paths except uses ID's as the keys and values.
//
// Leaves: ("Private") A slice of leaves (their seeds) in this tree down. Only
// calculated if LeafPathCalc.
//
// LeavesID: ("Public") Like Leaves except uses ID's as the keys and values.
//
// ## Stats and Internal Variables
//
// TotalLeaves: (Breaks one-way design pattern) Number of leaves currently
// populated in the whole tree, including "skip". (Children trees
// `TreeTotalLeaves` will be parent tree's `TreeTotalLeaves` as well.) Leaves
// are the last branch with no children.
//
// MaxTotalLeaves: (Breaks one-way design pattern) Normally left empty. Total
// leaves in the whole tree. (recursive data structure). If TotalLeaves is
// empty, each branch is fully populated based on the BLS. If TotalLeaves is
// populated, the last populated branch may be unsymmetrical when the
// MaxTotalLeaves limit is reached.
//
// ## Children
//
// Children: Recursive tree. Each digest in "Branch" is the key for the tree in
// "BranchTree" on a one to one basis. Note that the "public" value for the
// branch is used for the generation of a child tree, and thus becomes "private"
// from that perspective.
type Tree struct {
Alg coze.HshAlg `json:"alg"`
Seed coze.B64 `json:"seed"`
ID coze.B64 `json:"id"`
BLS []int `json:"branch_level_sizes,omitempty"`
Skip int `json:"skip,omitempty"`
Branches []coze.B64 `json:"branches,omitempty"`
PathCalc bool `json:"path_calc,omitempty"`
Paths B64MapP `json:"paths,omitempty"`
PathsID B64MapP `json:"paths_id,omitempty"`
Leaves []coze.B64 `json:"leaves,omitempty"`
LeavesID []coze.B64 `json:"leaves_id,omitempty"`
// Stats and internal variables.
TotalLeaves *int `json:"-"` // For whole tree, up and down.
MaxTotalLeaves *int `json:"-"` // For whole tree, up and down.
// Recursive struct variable. Last position in for easier reading.
Children []*Tree `json:"children,omitempty"`
}
// TODO
type TreeStats struct {
TotalLeaves int `json:"total_leaves,omitempty"`
MaxTotalLeaves int `json:"max_total_leaves,omitempty"`
}
// Populate needs alg and seed set before calling, and should set size if tree
// is non-empty. Does not populate trunk.
func (t *Tree) Populate() (err error) {
//fmt.Printf("Populate tree: %+v\n", t)
err = t.GenTreeBranches()
if err != nil {
return err
}
if len(t.BLS) > 1 { // Recursive tree.
t.Children = make([]*Tree, t.BLS[0])
for i := 0; i < t.BLS[0]; i++ {
tt := new(Tree)
tt.Alg = t.Alg
tt.Seed = t.Branches[i]
tt.BLS = t.BLS[1:]
tt.MaxTotalLeaves = t.MaxTotalLeaves
tt.TotalLeaves = t.TotalLeaves
tt.PathCalc = t.PathCalc
err = tt.Populate()
if err != nil {
return err
}
t.Children[i] = tt
if t.PathCalc {
t.Leaves = append(t.Leaves, tt.Leaves...) // Add leaves from branch to parent's leaves.
ps := []coze.B64{t.Seed} // Add parent to each child's path.
for k, v := range tt.Paths { // TODO need to make new unique paths, not all paths.(Use the pointer)
paths := append(ps, *v...)
t.Paths[k] = &paths
}
}
}
}
t.LeavesID = make([]coze.B64, 0, len(t.Leaves)) // Preallocate for performance.
for _, v := range t.Leaves { // TODO use []*[]coze.B64 for memory/processing efficiency.
id, err := Identity(t.Alg, v)
if err != nil {
return err
}
t.LeavesID = append(t.LeavesID, id)
}
return t.CalcPathsID()
}
// GenTreeBranches generates a slice of digests from a seed digest for the
// current level (non-recursive). See notes on "Tree". Resulting digests are
// not cryptographically related.
//
// Skip is the starting position for the main trunk (should typically be 0).
//
// S─(S)─► ID
// │
// ├─(S+0)──► D1
// │
// ├─(S+1)──► D2
// │
// ├─(S+2)──► D3
// ...
func (t *Tree) GenTreeBranches() (err error) {
if len(t.Seed) != t.Alg.Size() {
return fmt.Errorf("seed length %d does not match hashing algorithm size %d. ", len(t.Seed), t.Alg.Size())
}
t.ID, err = Identity(t.Alg, t.Seed)
if err != nil {
return err
}
if t.TotalLeaves == nil {
t.TotalLeaves = new(int)
*t.TotalLeaves = t.Skip
}
t.Branches = make([]coze.B64, t.BLS[0]-t.Skip)
t.Paths = make(B64MapP)
path := &[]coze.B64{t.Seed} // All children in a branch share the same Path (a pointer).
for i := 0; i < len(t.Branches); i++ {
//fmt.Printf("Current Leaves: %d\n", *t.TreeTotalLeaves)
if t.MaxTotalLeaves != nil && *t.TotalLeaves == *t.MaxTotalLeaves { // Max condition
return nil
}
node, err := Branch(t.Alg, t.Seed, i+t.Skip)
if err != nil {
return err
}
t.Branches[i] = node
if len(t.BLS) == 1 { // Increment Leaves. (Not branches)
//fmt.Printf("GenTreeBranches Edge of tree %t %+v \n", t.PathCalc, node)
*t.TotalLeaves++
if t.PathCalc {
t.Leaves = append(t.Leaves, node)
}
}
if t.PathCalc {
t.Paths[SB64(node)] = path
}
}
return nil
}
// CalcPathsID converts Tree.Paths from "seed"/"private" form to "id"/"public"
// form and sets Tree.IDPaths.
func (t *Tree) CalcPathsID() (err error) {
if !t.PathCalc {
return
}
t.PathsID = make(B64MapP)
for k, v := range t.Paths {
kid, err := Identity(t.Alg, coze.B64(k))
if err != nil {
return err
}
var pathsId []coze.B64
for _, v1 := range *v {
vid, err := Identity(t.Alg, v1)
if err != nil {
return err
}
pathsId = append(pathsId, vid)
}
t.PathsID[SB64(kid)] = &pathsId
}
return nil
}
// Identity is the identity function for a seed. `S─(S)─► ID` The result of
// the identity function is the "identifier". All nodes are seeds from a single
// tree perspective.
func Identity(alg coze.HshAlg, b coze.B64) (coze.B64, error) {
return coze.Hash(alg, b)
}
// Branch is the branch function for a seed. `S─(S||byte 0)──► S0` The result
// of this branch function is the "branch seed", which is the leaf at the tip of
// the tree.
func Branch(alg coze.HshAlg, dig coze.B64, i int) (coze.B64, error) {
return coze.Hash(alg, append(dig, IntToBytesBE(uint64(i))...))
}
// IntToBytesLE is like binary.LittleEndian.PutUint64 except it does not include
// empty padding bytes and always returns at least one byte. (Empty padding
// bytes on right, e.g. decimal 65,536 is [0 0 1].) As currently written, it
// only supports numbers up to 2^64 (18,446,744,073,709,551,615) which is the
// max value for uint64.
func IntToBytesLE(i uint64) []byte {
n := 8
b := make([]byte, n)
binary.LittleEndian.PutUint64(b, i)
for n > 1 && b[n-1] == 0 {
n--
}
return b[:n]
}
// IntToBytesBE is like binary.BigEndian.PutUint64 except it does not include
// empty padding bytes and always returns at least one byte. (Empty padding
// bytes on left, e.g. decimal 65,536 is bytes [1 0 0].) As currently written,
// it only supports numbers up to 2^64 (18,446,744,073,709,551,615) which is the
// max value for uint64.
func IntToBytesBE(i uint64) []byte {
n := 8
b := make([]byte, n)
binary.BigEndian.PutUint64(b, i)
j := 0
for j < n-1 && b[j] == 0 {
j++
}
return b[j:]
}
// NewTreePopulated is a simple helper for making trees.
func NewTreePopulated(alg coze.HshAlg, seed coze.B64, bls []int) (*Tree, error) {
t := Tree{
Alg: alg,
Seed: seed,
BLS: bls,
}
err := t.Populate()
return &t, err
}
func (t Tree) String() (s string) {
b, err := coze.MarshalPretty(t)
if err != nil {
return err.Error()
}
return string(b)
}