@osmura/merkletreejs 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/Base.d.ts +212 -0
- package/dist/Base.js +379 -0
- package/dist/IncrementalMerkleTree.d.ts +36 -0
- package/dist/IncrementalMerkleTree.js +252 -0
- package/dist/MerkleMountainRange.d.ts +95 -0
- package/dist/MerkleMountainRange.js +436 -0
- package/dist/MerkleRadixTree.d.ts +33 -0
- package/dist/MerkleRadixTree.js +152 -0
- package/dist/MerkleSumTree.d.ts +37 -0
- package/dist/MerkleSumTree.js +138 -0
- package/dist/MerkleTree.d.ts +570 -0
- package/dist/MerkleTree.js +1326 -0
- package/dist/UnifiedBinaryTree.d.ts +454 -0
- package/dist/UnifiedBinaryTree.js +829 -0
- package/dist/functional.d.ts +255 -0
- package/dist/functional.js +360 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +30 -0
- package/merkletree.js +1 -0
- package/package.json +155 -0
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MerkleTree = void 0;
|
|
7
|
+
const buffer_1 = require("buffer");
|
|
8
|
+
const buffer_reverse_1 = __importDefault(require("buffer-reverse"));
|
|
9
|
+
const sha256_1 = __importDefault(require("crypto-js/sha256"));
|
|
10
|
+
const treeify_1 = __importDefault(require("treeify"));
|
|
11
|
+
const Base_1 = __importDefault(require("./Base"));
|
|
12
|
+
/**
|
|
13
|
+
* Class reprensenting a Merkle Tree
|
|
14
|
+
* @namespace MerkleTree
|
|
15
|
+
*/
|
|
16
|
+
class MerkleTree extends Base_1.default {
|
|
17
|
+
/**
|
|
18
|
+
* @desc Constructs a Merkle Tree.
|
|
19
|
+
* All nodes and leaves are stored as Buffers.
|
|
20
|
+
* Lonely leaf nodes are promoted to the next level up without being hashed again.
|
|
21
|
+
* @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer.
|
|
22
|
+
* @param {Function} hashFunction - Hash function to use for hashing leaves and nodes
|
|
23
|
+
* @param {Object} options - Additional options
|
|
24
|
+
* @example
|
|
25
|
+
*```js
|
|
26
|
+
*const MerkleTree = require('merkletreejs')
|
|
27
|
+
*const crypto = require('crypto')
|
|
28
|
+
*
|
|
29
|
+
*function sha256(data) {
|
|
30
|
+
* // returns Buffer
|
|
31
|
+
* return crypto.createHash('sha256').update(data).digest()
|
|
32
|
+
*}
|
|
33
|
+
*
|
|
34
|
+
*const leaves = ['a', 'b', 'c'].map(value => keccak(value))
|
|
35
|
+
*
|
|
36
|
+
*const tree = new MerkleTree(leaves, sha256)
|
|
37
|
+
*```
|
|
38
|
+
*/
|
|
39
|
+
constructor(leaves, hashFn = sha256_1.default, options = {}) {
|
|
40
|
+
super();
|
|
41
|
+
this.duplicateOdd = false;
|
|
42
|
+
this.hashLeaves = false;
|
|
43
|
+
this.isBitcoinTree = false;
|
|
44
|
+
this.leaves = [];
|
|
45
|
+
this.layers = [];
|
|
46
|
+
this.sortLeaves = false;
|
|
47
|
+
this.sortPairs = false;
|
|
48
|
+
this.sort = false;
|
|
49
|
+
this.fillDefaultHash = null;
|
|
50
|
+
this.complete = false;
|
|
51
|
+
if (options.complete) {
|
|
52
|
+
if (options.isBitcoinTree) {
|
|
53
|
+
throw new Error('option "complete" is incompatible with "isBitcoinTree"');
|
|
54
|
+
}
|
|
55
|
+
if (options.duplicateOdd) {
|
|
56
|
+
throw new Error('option "complete" is incompatible with "duplicateOdd"');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.isBitcoinTree = !!options.isBitcoinTree;
|
|
60
|
+
this.hashLeaves = !!options.hashLeaves;
|
|
61
|
+
this.sortLeaves = !!options.sortLeaves;
|
|
62
|
+
this.sortPairs = !!options.sortPairs;
|
|
63
|
+
this.complete = !!options.complete;
|
|
64
|
+
if (options.fillDefaultHash) {
|
|
65
|
+
if (typeof options.fillDefaultHash === 'function') {
|
|
66
|
+
this.fillDefaultHash = options.fillDefaultHash;
|
|
67
|
+
}
|
|
68
|
+
else if (buffer_1.Buffer.isBuffer(options.fillDefaultHash) || typeof options.fillDefaultHash === 'string') {
|
|
69
|
+
this.fillDefaultHash = (idx, hashFn) => options.fillDefaultHash;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error('method "fillDefaultHash" must be a function, Buffer, or string');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.sort = !!options.sort;
|
|
76
|
+
if (this.sort) {
|
|
77
|
+
this.sortLeaves = true;
|
|
78
|
+
this.sortPairs = true;
|
|
79
|
+
}
|
|
80
|
+
this.duplicateOdd = !!options.duplicateOdd;
|
|
81
|
+
if (options.concatenator) {
|
|
82
|
+
this.concatenator = options.concatenator;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.concatenator = buffer_1.Buffer.concat;
|
|
86
|
+
}
|
|
87
|
+
if (typeof hashFn !== 'function') {
|
|
88
|
+
throw new Error('hashFn must be a function');
|
|
89
|
+
}
|
|
90
|
+
this.hashFn = this.bufferifyFn(hashFn);
|
|
91
|
+
this.processLeaves(leaves);
|
|
92
|
+
}
|
|
93
|
+
getOptions() {
|
|
94
|
+
var _a, _b;
|
|
95
|
+
return {
|
|
96
|
+
complete: this.complete,
|
|
97
|
+
isBitcoinTree: this.isBitcoinTree,
|
|
98
|
+
hashLeaves: this.hashLeaves,
|
|
99
|
+
sortLeaves: this.sortLeaves,
|
|
100
|
+
sortPairs: this.sortPairs,
|
|
101
|
+
sort: this.sort,
|
|
102
|
+
fillDefaultHash: (_b = (_a = this.fillDefaultHash) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : null,
|
|
103
|
+
duplicateOdd: this.duplicateOdd
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
processLeaves(leaves) {
|
|
107
|
+
if (this.hashLeaves) {
|
|
108
|
+
leaves = leaves.map(this.hashFn);
|
|
109
|
+
}
|
|
110
|
+
this.leaves = leaves.map(this.bufferify);
|
|
111
|
+
if (this.sortLeaves) {
|
|
112
|
+
this.leaves = this.leaves.sort(buffer_1.Buffer.compare);
|
|
113
|
+
}
|
|
114
|
+
if (this.fillDefaultHash) {
|
|
115
|
+
for (let i = this.leaves.length; i < Math.pow(2, Math.ceil(Math.log2(this.leaves.length))); i++) {
|
|
116
|
+
this.leaves.push(this.bufferify(this.fillDefaultHash(i, this.hashFn)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.createHashes(this.leaves);
|
|
120
|
+
}
|
|
121
|
+
createHashes(nodes) {
|
|
122
|
+
this.layers = [nodes];
|
|
123
|
+
while (nodes.length > 1) {
|
|
124
|
+
const layerIndex = this.layers.length;
|
|
125
|
+
this.layers.push([]);
|
|
126
|
+
const layerLimit = this.complete && layerIndex === 1 && !Number.isInteger(Math.log2(nodes.length))
|
|
127
|
+
? (2 * nodes.length) - (Math.pow(2, Math.ceil(Math.log2(nodes.length))))
|
|
128
|
+
: nodes.length;
|
|
129
|
+
for (let i = 0; i < nodes.length; i += 2) {
|
|
130
|
+
if (i >= layerLimit) {
|
|
131
|
+
this.layers[layerIndex].push(...nodes.slice(layerLimit));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
else if (i + 1 === nodes.length) {
|
|
135
|
+
if (nodes.length % 2 === 1) {
|
|
136
|
+
const data = nodes[nodes.length - 1];
|
|
137
|
+
let hash = data;
|
|
138
|
+
// is bitcoin tree
|
|
139
|
+
if (this.isBitcoinTree) {
|
|
140
|
+
// Bitcoin method of duplicating the odd ending nodes
|
|
141
|
+
hash = this.hashFn(this.concatenator([buffer_reverse_1.default(data), buffer_reverse_1.default(data)]));
|
|
142
|
+
hash = buffer_reverse_1.default(this.hashFn(hash));
|
|
143
|
+
this.layers[layerIndex].push(hash);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (this.duplicateOdd) {
|
|
148
|
+
// continue with creating layer
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// push copy of hash and continue iteration
|
|
152
|
+
this.layers[layerIndex].push(nodes[i]);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const left = nodes[i];
|
|
159
|
+
const right = i + 1 === nodes.length ? left : nodes[i + 1];
|
|
160
|
+
let combined = null;
|
|
161
|
+
if (this.isBitcoinTree) {
|
|
162
|
+
combined = [buffer_reverse_1.default(left), buffer_reverse_1.default(right)];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
combined = [left, right];
|
|
166
|
+
}
|
|
167
|
+
if (this.sortPairs) {
|
|
168
|
+
combined.sort(buffer_1.Buffer.compare);
|
|
169
|
+
}
|
|
170
|
+
let hash = this.hashFn(this.concatenator(combined));
|
|
171
|
+
// double hash if bitcoin tree
|
|
172
|
+
if (this.isBitcoinTree) {
|
|
173
|
+
hash = buffer_reverse_1.default(this.hashFn(hash));
|
|
174
|
+
}
|
|
175
|
+
this.layers[layerIndex].push(hash);
|
|
176
|
+
}
|
|
177
|
+
nodes = this.layers[layerIndex];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* addLeaf
|
|
182
|
+
* @desc Adds a leaf to the tree and re-calculates layers.
|
|
183
|
+
* @param {String|Buffer} - Leaf
|
|
184
|
+
* @param {Boolean} - Set to true if the leaf should be hashed before being added to tree.
|
|
185
|
+
* @example
|
|
186
|
+
*```js
|
|
187
|
+
*tree.addLeaf(newLeaf)
|
|
188
|
+
*```
|
|
189
|
+
*/
|
|
190
|
+
addLeaf(leaf, shouldHash = false) {
|
|
191
|
+
if (shouldHash) {
|
|
192
|
+
leaf = this.hashFn(leaf);
|
|
193
|
+
}
|
|
194
|
+
this.processLeaves(this.leaves.concat(leaf));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* addLeaves
|
|
198
|
+
* @desc Adds multiple leaves to the tree and re-calculates layers.
|
|
199
|
+
* @param {String[]|Buffer[]} - Array of leaves
|
|
200
|
+
* @param {Boolean} - Set to true if the leaves should be hashed before being added to tree.
|
|
201
|
+
* @example
|
|
202
|
+
*```js
|
|
203
|
+
*tree.addLeaves(newLeaves)
|
|
204
|
+
*```
|
|
205
|
+
*/
|
|
206
|
+
addLeaves(leaves, shouldHash = false) {
|
|
207
|
+
if (shouldHash) {
|
|
208
|
+
leaves = leaves.map(this.hashFn);
|
|
209
|
+
}
|
|
210
|
+
this.processLeaves(this.leaves.concat(leaves));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* getLeaves
|
|
214
|
+
* @desc Returns array of leaves of Merkle Tree.
|
|
215
|
+
* @return {Buffer[]}
|
|
216
|
+
* @example
|
|
217
|
+
*```js
|
|
218
|
+
*const leaves = tree.getLeaves()
|
|
219
|
+
*```
|
|
220
|
+
*/
|
|
221
|
+
getLeaves(values) {
|
|
222
|
+
if (Array.isArray(values)) {
|
|
223
|
+
if (this.hashLeaves) {
|
|
224
|
+
values = values.map(this.hashFn);
|
|
225
|
+
if (this.sortLeaves) {
|
|
226
|
+
values = values.sort(buffer_1.Buffer.compare);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return this.leaves.filter(leaf => this.bufferIndexOf(values, leaf, this.sortLeaves) !== -1);
|
|
230
|
+
}
|
|
231
|
+
return this.leaves;
|
|
232
|
+
}
|
|
233
|
+
removeLeaf(index) {
|
|
234
|
+
if (!this.isValidLeafIndex(index)) {
|
|
235
|
+
throw new Error(`"${index}" is not a valid leaf index. Expected to be [0, ${this.getLeafCount() - 1}]`);
|
|
236
|
+
}
|
|
237
|
+
const result = this.leaves.splice(index, 1);
|
|
238
|
+
this.processLeaves(this.leaves);
|
|
239
|
+
return result[0];
|
|
240
|
+
}
|
|
241
|
+
updateLeaf(index, value, shouldHash = false) {
|
|
242
|
+
if (!this.isValidLeafIndex(index)) {
|
|
243
|
+
throw new Error(`"${index}" is not a valid leaf index. Expected to be [0, ${this.getLeafCount() - 1}]`);
|
|
244
|
+
}
|
|
245
|
+
if (shouldHash)
|
|
246
|
+
value = this.hashFn(value);
|
|
247
|
+
this.leaves[index] = value;
|
|
248
|
+
this.processLeaves(this.leaves);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* getLeaf
|
|
252
|
+
* @desc Returns the leaf at the given index.
|
|
253
|
+
* @param {Number} - Index number
|
|
254
|
+
* @return {Buffer}
|
|
255
|
+
* @example
|
|
256
|
+
*```js
|
|
257
|
+
*const leaf = tree.getLeaf(1)
|
|
258
|
+
*```
|
|
259
|
+
*/
|
|
260
|
+
getLeaf(index) {
|
|
261
|
+
if (index < 0 || index > this.leaves.length - 1) {
|
|
262
|
+
return buffer_1.Buffer.from([]);
|
|
263
|
+
}
|
|
264
|
+
return this.leaves[index];
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* getHexLeaf
|
|
268
|
+
* @desc Returns the leaf at the given index as a hex string.
|
|
269
|
+
* @param {Number} - Index number
|
|
270
|
+
* @return {String}
|
|
271
|
+
* @example
|
|
272
|
+
*```js
|
|
273
|
+
*const leaf = tree.getHexLeaf(1)
|
|
274
|
+
*```
|
|
275
|
+
*/
|
|
276
|
+
getHexLeaf(index) {
|
|
277
|
+
return this.bufferToHex(this.getLeaf(index));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* getLeafIndex
|
|
281
|
+
* @desc Returns the index of the given leaf, or -1 if the leaf is not found.
|
|
282
|
+
* @param {String|Buffer} - Target leaf
|
|
283
|
+
* @return {number}
|
|
284
|
+
* @example
|
|
285
|
+
*```js
|
|
286
|
+
*const leaf = Buffer.from('abc')
|
|
287
|
+
*const index = tree.getLeafIndex(leaf)
|
|
288
|
+
*```
|
|
289
|
+
*/
|
|
290
|
+
getLeafIndex(target) {
|
|
291
|
+
target = this.bufferify(target);
|
|
292
|
+
const leaves = this.getLeaves();
|
|
293
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
294
|
+
const leaf = leaves[i];
|
|
295
|
+
if (leaf.equals(target)) {
|
|
296
|
+
return i;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return -1;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* getLeafCount
|
|
303
|
+
* @desc Returns the total number of leaves.
|
|
304
|
+
* @return {number}
|
|
305
|
+
* @example
|
|
306
|
+
*```js
|
|
307
|
+
*const count = tree.getLeafCount()
|
|
308
|
+
*```
|
|
309
|
+
*/
|
|
310
|
+
getLeafCount() {
|
|
311
|
+
return this.leaves.length;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* getHexLeaves
|
|
315
|
+
* @desc Returns array of leaves of Merkle Tree as hex strings.
|
|
316
|
+
* @return {String[]}
|
|
317
|
+
* @example
|
|
318
|
+
*```js
|
|
319
|
+
*const leaves = tree.getHexLeaves()
|
|
320
|
+
*```
|
|
321
|
+
*/
|
|
322
|
+
getHexLeaves() {
|
|
323
|
+
return this.leaves.map(leaf => this.bufferToHex(leaf));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* marshalLeaves
|
|
327
|
+
* @desc Returns array of leaves of Merkle Tree as a JSON string.
|
|
328
|
+
* @param {String[]|Buffer[]} - Merkle tree leaves
|
|
329
|
+
* @return {String} - List of leaves as JSON string
|
|
330
|
+
* @example
|
|
331
|
+
*```js
|
|
332
|
+
*const jsonStr = MerkleTree.marshalLeaves(leaves)
|
|
333
|
+
*```
|
|
334
|
+
*/
|
|
335
|
+
static marshalLeaves(leaves) {
|
|
336
|
+
return JSON.stringify(leaves.map(leaf => MerkleTree.bufferToHex(leaf)), null, 2);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* unmarshalLeaves
|
|
340
|
+
* @desc Returns array of leaves of Merkle Tree as a Buffers.
|
|
341
|
+
* @param {String|Object} - JSON stringified leaves
|
|
342
|
+
* @return {Buffer[]} - Unmarshalled list of leaves
|
|
343
|
+
* @example
|
|
344
|
+
*```js
|
|
345
|
+
*const leaves = MerkleTree.unmarshalLeaves(jsonStr)
|
|
346
|
+
*```
|
|
347
|
+
*/
|
|
348
|
+
static unmarshalLeaves(jsonStr) {
|
|
349
|
+
let parsed = null;
|
|
350
|
+
if (typeof jsonStr === 'string') {
|
|
351
|
+
parsed = JSON.parse(jsonStr);
|
|
352
|
+
}
|
|
353
|
+
else if (jsonStr instanceof Object) {
|
|
354
|
+
parsed = jsonStr;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
throw new Error('Expected type of string or object');
|
|
358
|
+
}
|
|
359
|
+
if (!parsed) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
if (!Array.isArray(parsed)) {
|
|
363
|
+
throw new Error('Expected JSON string to be array');
|
|
364
|
+
}
|
|
365
|
+
return parsed.map(MerkleTree.bufferify);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* getLayers
|
|
369
|
+
* @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root.
|
|
370
|
+
* @return {Buffer[][]}
|
|
371
|
+
* @example
|
|
372
|
+
*```js
|
|
373
|
+
*const layers = tree.getLayers()
|
|
374
|
+
*```
|
|
375
|
+
*/
|
|
376
|
+
getLayers() {
|
|
377
|
+
return this.layers;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* getHexLayers
|
|
381
|
+
* @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root as hex strings.
|
|
382
|
+
* @return {String[][]}
|
|
383
|
+
* @example
|
|
384
|
+
*```js
|
|
385
|
+
*const layers = tree.getHexLayers()
|
|
386
|
+
*```
|
|
387
|
+
*/
|
|
388
|
+
getHexLayers() {
|
|
389
|
+
return this.layers.reduce((acc, item) => {
|
|
390
|
+
if (Array.isArray(item)) {
|
|
391
|
+
acc.push(item.map(layer => this.bufferToHex(layer)));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
acc.push(item);
|
|
395
|
+
}
|
|
396
|
+
return acc;
|
|
397
|
+
}, []);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* getLayersFlat
|
|
401
|
+
* @desc Returns single flat array of all layers of Merkle Tree, including leaves and root.
|
|
402
|
+
* @return {Buffer[]}
|
|
403
|
+
* @example
|
|
404
|
+
*```js
|
|
405
|
+
*const layers = tree.getLayersFlat()
|
|
406
|
+
*```
|
|
407
|
+
*/
|
|
408
|
+
getLayersFlat() {
|
|
409
|
+
const layers = this.layers.reduce((acc, item) => {
|
|
410
|
+
if (Array.isArray(item)) {
|
|
411
|
+
acc.unshift(...item);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
acc.unshift(item);
|
|
415
|
+
}
|
|
416
|
+
return acc;
|
|
417
|
+
}, []);
|
|
418
|
+
layers.unshift(buffer_1.Buffer.from([0]));
|
|
419
|
+
return layers;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* getHexLayersFlat
|
|
423
|
+
* @desc Returns single flat array of all layers of Merkle Tree, including leaves and root as hex string.
|
|
424
|
+
* @return {String[]}
|
|
425
|
+
* @example
|
|
426
|
+
*```js
|
|
427
|
+
*const layers = tree.getHexLayersFlat()
|
|
428
|
+
*```
|
|
429
|
+
*/
|
|
430
|
+
getHexLayersFlat() {
|
|
431
|
+
return this.getLayersFlat().map(layer => this.bufferToHex(layer));
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* getLayerCount
|
|
435
|
+
* @desc Returns the total number of layers.
|
|
436
|
+
* @return {number}
|
|
437
|
+
* @example
|
|
438
|
+
*```js
|
|
439
|
+
*const count = tree.getLayerCount()
|
|
440
|
+
*```
|
|
441
|
+
*/
|
|
442
|
+
getLayerCount() {
|
|
443
|
+
return this.getLayers().length;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* getRoot
|
|
447
|
+
* @desc Returns the Merkle root hash as a Buffer.
|
|
448
|
+
* @return {Buffer}
|
|
449
|
+
* @example
|
|
450
|
+
*```js
|
|
451
|
+
*const root = tree.getRoot()
|
|
452
|
+
*```
|
|
453
|
+
*/
|
|
454
|
+
getRoot() {
|
|
455
|
+
if (this.layers.length === 0) {
|
|
456
|
+
return buffer_1.Buffer.from([]);
|
|
457
|
+
}
|
|
458
|
+
return this.layers[this.layers.length - 1][0] || buffer_1.Buffer.from([]);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* getHexRoot
|
|
462
|
+
* @desc Returns the Merkle root hash as a hex string.
|
|
463
|
+
* @return {String}
|
|
464
|
+
* @example
|
|
465
|
+
*```js
|
|
466
|
+
*const root = tree.getHexRoot()
|
|
467
|
+
*```
|
|
468
|
+
*/
|
|
469
|
+
getHexRoot() {
|
|
470
|
+
return this.bufferToHex(this.getRoot());
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* getProof
|
|
474
|
+
* @desc Returns the proof for a target leaf.
|
|
475
|
+
* @param {Buffer} leaf - Target leaf
|
|
476
|
+
* @param {Number} [index] - Target leaf index in leaves array.
|
|
477
|
+
* Use if there are leaves containing duplicate data in order to distinguish it.
|
|
478
|
+
* @return {Object[]} - Array of objects containing a position property of type string
|
|
479
|
+
* with values of 'left' or 'right' and a data property of type Buffer.
|
|
480
|
+
* @example
|
|
481
|
+
* ```js
|
|
482
|
+
*const proof = tree.getProof(leaves[2])
|
|
483
|
+
*```
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
*```js
|
|
487
|
+
*const leaves = ['a', 'b', 'a'].map(value => keccak(value))
|
|
488
|
+
*const tree = new MerkleTree(leaves, keccak)
|
|
489
|
+
*const proof = tree.getProof(leaves[2], 2)
|
|
490
|
+
*```
|
|
491
|
+
*/
|
|
492
|
+
getProof(leaf, index) {
|
|
493
|
+
if (typeof leaf === 'undefined') {
|
|
494
|
+
throw new Error('leaf is required');
|
|
495
|
+
}
|
|
496
|
+
leaf = this.bufferify(leaf);
|
|
497
|
+
const proof = [];
|
|
498
|
+
if (!Number.isInteger(index)) {
|
|
499
|
+
index = -1;
|
|
500
|
+
for (let i = 0; i < this.leaves.length; i++) {
|
|
501
|
+
if (buffer_1.Buffer.compare(leaf, this.leaves[i]) === 0) {
|
|
502
|
+
index = i;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (index <= -1) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
510
|
+
const layer = this.layers[i];
|
|
511
|
+
const isRightNode = index % 2;
|
|
512
|
+
const pairIndex = (isRightNode ? index - 1
|
|
513
|
+
: this.isBitcoinTree && index === layer.length - 1 && i < this.layers.length - 1
|
|
514
|
+
// Proof Generation for Bitcoin Trees
|
|
515
|
+
? index
|
|
516
|
+
// Proof Generation for Non-Bitcoin Trees
|
|
517
|
+
: index + 1);
|
|
518
|
+
if (pairIndex < layer.length) {
|
|
519
|
+
proof.push({
|
|
520
|
+
position: isRightNode ? 'left' : 'right',
|
|
521
|
+
data: layer[pairIndex]
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// set index to parent index
|
|
525
|
+
index = (index / 2) | 0;
|
|
526
|
+
}
|
|
527
|
+
return proof;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* getHexProof
|
|
531
|
+
* @desc Returns the proof for a target leaf as hex strings.
|
|
532
|
+
* @param {Buffer} leaf - Target leaf
|
|
533
|
+
* @param {Number} [index] - Target leaf index in leaves array.
|
|
534
|
+
* Use if there are leaves containing duplicate data in order to distinguish it.
|
|
535
|
+
* @return {String[]} - Proof array as hex strings.
|
|
536
|
+
* @example
|
|
537
|
+
* ```js
|
|
538
|
+
*const proof = tree.getHexProof(leaves[2])
|
|
539
|
+
*```
|
|
540
|
+
*/
|
|
541
|
+
getHexProof(leaf, index) {
|
|
542
|
+
return this.getProof(leaf, index).map(item => this.bufferToHex(item.data));
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* getProofs
|
|
546
|
+
* @desc Returns the proofs for all leaves.
|
|
547
|
+
* @return {Object[]} - Array of objects containing a position property of type string
|
|
548
|
+
* with values of 'left' or 'right' and a data property of type Buffer for all leaves.
|
|
549
|
+
* @example
|
|
550
|
+
* ```js
|
|
551
|
+
*const proofs = tree.getProofs()
|
|
552
|
+
*```
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
*```js
|
|
556
|
+
*const leaves = ['a', 'b', 'a'].map(value => keccak(value))
|
|
557
|
+
*const tree = new MerkleTree(leaves, keccak)
|
|
558
|
+
*const proofs = tree.getProofs()
|
|
559
|
+
*```
|
|
560
|
+
*/
|
|
561
|
+
getProofs() {
|
|
562
|
+
const proof = [];
|
|
563
|
+
const proofs = [];
|
|
564
|
+
this.getProofsDFS(this.layers.length - 1, 0, proof, proofs);
|
|
565
|
+
return proofs;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* getProofsDFS
|
|
569
|
+
* @desc Get all proofs through single traverse
|
|
570
|
+
* @param {Number} currentLayer - Current layer index in traverse.
|
|
571
|
+
* @param {Number} index - Current tarvese node index in traverse.
|
|
572
|
+
* @param {Object[]} proof - Proof chain for single leaf.
|
|
573
|
+
* @param {Object[]} proofs - Proofs for all leaves
|
|
574
|
+
* @example
|
|
575
|
+
* ```js
|
|
576
|
+
*const layers = tree.getLayers()
|
|
577
|
+
*const index = 0;
|
|
578
|
+
*let proof = [];
|
|
579
|
+
*let proofs = [];
|
|
580
|
+
*const proof = tree.getProofsDFS(layers, index, proof, proofs)
|
|
581
|
+
*```
|
|
582
|
+
*/
|
|
583
|
+
getProofsDFS(currentLayer, index, proof, proofs) {
|
|
584
|
+
const isRightNode = index % 2;
|
|
585
|
+
if (currentLayer === -1) {
|
|
586
|
+
if (!isRightNode)
|
|
587
|
+
proofs.push([...proof].reverse());
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (index >= this.layers[currentLayer].length)
|
|
591
|
+
return;
|
|
592
|
+
const layer = this.layers[currentLayer];
|
|
593
|
+
const pairIndex = isRightNode ? index - 1 : index + 1;
|
|
594
|
+
let pushed = false;
|
|
595
|
+
if (pairIndex < layer.length) {
|
|
596
|
+
pushed = true;
|
|
597
|
+
proof.push({
|
|
598
|
+
position: isRightNode ? 'left' : 'right',
|
|
599
|
+
data: layer[pairIndex]
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
const leftchildIndex = index * 2;
|
|
603
|
+
const rightchildIndex = index * 2 + 1;
|
|
604
|
+
this.getProofsDFS(currentLayer - 1, leftchildIndex, proof, proofs);
|
|
605
|
+
this.getProofsDFS(currentLayer - 1, rightchildIndex, proof, proofs);
|
|
606
|
+
if (pushed)
|
|
607
|
+
proof.splice(proof.length - 1, 1);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* getHexProofs
|
|
611
|
+
* @desc Returns the proofs for all leaves as hex strings.
|
|
612
|
+
* @return {String[]} - Proofs array as hex strings.
|
|
613
|
+
* @example
|
|
614
|
+
* ```js
|
|
615
|
+
*const proofs = tree.getHexProofs()
|
|
616
|
+
*```
|
|
617
|
+
*/
|
|
618
|
+
getHexProofs() {
|
|
619
|
+
return this.getProofs().map(item => this.bufferToHex(item.data));
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* getPositionalHexProof
|
|
623
|
+
* @desc Returns the proof for a target leaf as hex strings and the position in binary (left == 0).
|
|
624
|
+
* @param {Buffer} leaf - Target leaf
|
|
625
|
+
* @param {Number} [index] - Target leaf index in leaves array.
|
|
626
|
+
* Use if there are leaves containing duplicate data in order to distinguish it.
|
|
627
|
+
* @return {(string | number)[][]} - Proof array as hex strings. position at index 0
|
|
628
|
+
* @example
|
|
629
|
+
* ```js
|
|
630
|
+
*const proof = tree.getPositionalHexProof(leaves[2])
|
|
631
|
+
*```
|
|
632
|
+
*/
|
|
633
|
+
getPositionalHexProof(leaf, index) {
|
|
634
|
+
return this.getProof(leaf, index).map(item => {
|
|
635
|
+
return [
|
|
636
|
+
item.position === 'left' ? 0 : 1,
|
|
637
|
+
this.bufferToHex(item.data)
|
|
638
|
+
];
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* marshalProof
|
|
643
|
+
* @desc Returns proof array as JSON string.
|
|
644
|
+
* @param {String[]|Object[]} proof - Merkle tree proof array
|
|
645
|
+
* @return {String} - Proof array as JSON string.
|
|
646
|
+
* @example
|
|
647
|
+
* ```js
|
|
648
|
+
*const jsonStr = MerkleTree.marshalProof(proof)
|
|
649
|
+
*```
|
|
650
|
+
*/
|
|
651
|
+
static marshalProof(proof) {
|
|
652
|
+
const json = proof.map(item => {
|
|
653
|
+
if (typeof item === 'string') {
|
|
654
|
+
return item;
|
|
655
|
+
}
|
|
656
|
+
if (buffer_1.Buffer.isBuffer(item)) {
|
|
657
|
+
return MerkleTree.bufferToHex(item);
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
position: item.position,
|
|
661
|
+
data: MerkleTree.bufferToHex(item.data)
|
|
662
|
+
};
|
|
663
|
+
});
|
|
664
|
+
return JSON.stringify(json, null, 2);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* unmarshalProof
|
|
668
|
+
* @desc Returns the proof for a target leaf as a list of Buffers.
|
|
669
|
+
* @param {String|Object} - Merkle tree leaves
|
|
670
|
+
* @return {String|Object} - Marshalled proof
|
|
671
|
+
* @example
|
|
672
|
+
* ```js
|
|
673
|
+
*const proof = MerkleTree.unmarshalProof(jsonStr)
|
|
674
|
+
*```
|
|
675
|
+
*/
|
|
676
|
+
static unmarshalProof(jsonStr) {
|
|
677
|
+
let parsed = null;
|
|
678
|
+
if (typeof jsonStr === 'string') {
|
|
679
|
+
parsed = JSON.parse(jsonStr);
|
|
680
|
+
}
|
|
681
|
+
else if (jsonStr instanceof Object) {
|
|
682
|
+
parsed = jsonStr;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
throw new Error('Expected type of string or object');
|
|
686
|
+
}
|
|
687
|
+
if (!parsed) {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
if (!Array.isArray(parsed)) {
|
|
691
|
+
throw new Error('Expected JSON string to be array');
|
|
692
|
+
}
|
|
693
|
+
return parsed.map(item => {
|
|
694
|
+
if (typeof item === 'string') {
|
|
695
|
+
return MerkleTree.bufferify(item);
|
|
696
|
+
}
|
|
697
|
+
else if (item instanceof Object) {
|
|
698
|
+
return {
|
|
699
|
+
position: item.position,
|
|
700
|
+
data: MerkleTree.bufferify(item.data)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
throw new Error('Expected item to be of type string or object');
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
static marshalTree(tree) {
|
|
709
|
+
const root = tree.getHexRoot();
|
|
710
|
+
const leaves = tree.leaves.map(leaf => MerkleTree.bufferToHex(leaf));
|
|
711
|
+
const layers = tree.getHexLayers();
|
|
712
|
+
const options = tree.getOptions();
|
|
713
|
+
return JSON.stringify({
|
|
714
|
+
options,
|
|
715
|
+
root,
|
|
716
|
+
layers,
|
|
717
|
+
leaves
|
|
718
|
+
}, null, 2);
|
|
719
|
+
}
|
|
720
|
+
static unmarshalTree(jsonStr, hashFn = sha256_1.default, options = {}) {
|
|
721
|
+
let parsed = null;
|
|
722
|
+
if (typeof jsonStr === 'string') {
|
|
723
|
+
parsed = JSON.parse(jsonStr);
|
|
724
|
+
}
|
|
725
|
+
else if (jsonStr instanceof Object) {
|
|
726
|
+
parsed = jsonStr;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
throw new Error('Expected type of string or object');
|
|
730
|
+
}
|
|
731
|
+
if (!parsed) {
|
|
732
|
+
throw new Error('could not parse json');
|
|
733
|
+
}
|
|
734
|
+
options = Object.assign({}, parsed.options || {}, options);
|
|
735
|
+
return new MerkleTree(parsed.leaves, hashFn, options);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* getProofIndices
|
|
739
|
+
* @desc Returns the proof indices for given tree indices.
|
|
740
|
+
* @param {Number[]} treeIndices - Tree indices
|
|
741
|
+
* @param {Number} depth - Tree depth; number of layers.
|
|
742
|
+
* @return {Number[]} - Proof indices
|
|
743
|
+
* @example
|
|
744
|
+
* ```js
|
|
745
|
+
*const proofIndices = tree.getProofIndices([2,5,6], 4)
|
|
746
|
+
*console.log(proofIndices) // [ 23, 20, 19, 8, 3 ]
|
|
747
|
+
*```
|
|
748
|
+
*/
|
|
749
|
+
getProofIndices(treeIndices, depth) {
|
|
750
|
+
const leafCount = Math.pow(2, depth);
|
|
751
|
+
let maximalIndices = new Set();
|
|
752
|
+
for (const index of treeIndices) {
|
|
753
|
+
let x = leafCount + index;
|
|
754
|
+
while (x > 1) {
|
|
755
|
+
maximalIndices.add(x ^ 1);
|
|
756
|
+
x = (x / 2) | 0;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const a = treeIndices.map(index => leafCount + index);
|
|
760
|
+
const b = Array.from(maximalIndices).sort((a, b) => a - b).reverse();
|
|
761
|
+
maximalIndices = a.concat(b);
|
|
762
|
+
const redundantIndices = new Set();
|
|
763
|
+
const proof = [];
|
|
764
|
+
for (let index of maximalIndices) {
|
|
765
|
+
if (!redundantIndices.has(index)) {
|
|
766
|
+
proof.push(index);
|
|
767
|
+
while (index > 1) {
|
|
768
|
+
redundantIndices.add(index);
|
|
769
|
+
if (!redundantIndices.has(index ^ 1))
|
|
770
|
+
break;
|
|
771
|
+
index = (index / 2) | 0;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return proof.filter(index => {
|
|
776
|
+
return !treeIndices.includes(index - leafCount);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
getProofIndicesForUnevenTree(sortedLeafIndices, leavesCount) {
|
|
780
|
+
const depth = Math.ceil(Math.log2(leavesCount));
|
|
781
|
+
const unevenLayers = [];
|
|
782
|
+
for (let index = 0; index < depth; index++) {
|
|
783
|
+
const unevenLayer = leavesCount % 2 !== 0;
|
|
784
|
+
if (unevenLayer) {
|
|
785
|
+
unevenLayers.push({ index, leavesCount });
|
|
786
|
+
}
|
|
787
|
+
leavesCount = Math.ceil(leavesCount / 2);
|
|
788
|
+
}
|
|
789
|
+
const proofIndices = [];
|
|
790
|
+
let layerNodes = sortedLeafIndices;
|
|
791
|
+
for (let layerIndex = 0; layerIndex < depth; layerIndex++) {
|
|
792
|
+
const siblingIndices = layerNodes.map((index) => {
|
|
793
|
+
if (index % 2 === 0) {
|
|
794
|
+
return index + 1;
|
|
795
|
+
}
|
|
796
|
+
return index - 1;
|
|
797
|
+
});
|
|
798
|
+
let proofNodeIndices = siblingIndices.filter((index) => !layerNodes.includes(index));
|
|
799
|
+
const unevenLayer = unevenLayers.find(({ index }) => index === layerIndex);
|
|
800
|
+
if (unevenLayer && layerNodes.includes(unevenLayer.leavesCount - 1)) {
|
|
801
|
+
proofNodeIndices = proofNodeIndices.slice(0, -1);
|
|
802
|
+
}
|
|
803
|
+
proofIndices.push(proofNodeIndices);
|
|
804
|
+
layerNodes = [...new Set(layerNodes.map((index) => {
|
|
805
|
+
if (index % 2 === 0) {
|
|
806
|
+
return index / 2;
|
|
807
|
+
}
|
|
808
|
+
if (index % 2 === 0) {
|
|
809
|
+
return (index + 1) / 2;
|
|
810
|
+
}
|
|
811
|
+
return (index - 1) / 2;
|
|
812
|
+
}))];
|
|
813
|
+
}
|
|
814
|
+
return proofIndices;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* getMultiProof
|
|
818
|
+
* @desc Returns the multiproof for given tree indices.
|
|
819
|
+
* @param {Number[]} indices - Tree indices.
|
|
820
|
+
* @return {Buffer[]} - Multiproofs
|
|
821
|
+
* @example
|
|
822
|
+
* ```js
|
|
823
|
+
*const indices = [2, 5, 6]
|
|
824
|
+
*const proof = tree.getMultiProof(indices)
|
|
825
|
+
*```
|
|
826
|
+
*/
|
|
827
|
+
getMultiProof(tree, indices) {
|
|
828
|
+
if (!this.complete) {
|
|
829
|
+
console.warn('Warning: For correct multiProofs it\'s strongly recommended to set complete: true');
|
|
830
|
+
}
|
|
831
|
+
if (!indices) {
|
|
832
|
+
indices = tree;
|
|
833
|
+
tree = this.getLayersFlat();
|
|
834
|
+
}
|
|
835
|
+
const isUneven = this.isUnevenTree();
|
|
836
|
+
if (isUneven) {
|
|
837
|
+
if (indices.every(Number.isInteger)) {
|
|
838
|
+
return this.getMultiProofForUnevenTree(indices);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (!indices.every(Number.isInteger)) {
|
|
842
|
+
let els = indices;
|
|
843
|
+
if (this.sortPairs) {
|
|
844
|
+
els = els.sort(buffer_1.Buffer.compare);
|
|
845
|
+
}
|
|
846
|
+
let ids = els.map((el) => this.bufferIndexOf(this.leaves, el, this.sortLeaves)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1);
|
|
847
|
+
if (!ids.every((idx) => idx !== -1)) {
|
|
848
|
+
throw new Error('Element does not exist in Merkle tree');
|
|
849
|
+
}
|
|
850
|
+
const hashes = [];
|
|
851
|
+
const proof = [];
|
|
852
|
+
let nextIds = [];
|
|
853
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
854
|
+
const layer = this.layers[i];
|
|
855
|
+
for (let j = 0; j < ids.length; j++) {
|
|
856
|
+
const idx = ids[j];
|
|
857
|
+
const pairElement = this.getPairNode(layer, idx);
|
|
858
|
+
hashes.push(layer[idx]);
|
|
859
|
+
if (pairElement) {
|
|
860
|
+
proof.push(pairElement);
|
|
861
|
+
}
|
|
862
|
+
nextIds.push((idx / 2) | 0);
|
|
863
|
+
}
|
|
864
|
+
ids = nextIds.filter((value, i, self) => self.indexOf(value) === i);
|
|
865
|
+
nextIds = [];
|
|
866
|
+
}
|
|
867
|
+
return proof.filter((value) => !hashes.includes(value));
|
|
868
|
+
}
|
|
869
|
+
return this.getProofIndices(indices, Math.log2((tree.length / 2) | 0)).map(index => tree[index]);
|
|
870
|
+
}
|
|
871
|
+
getMultiProofForUnevenTree(tree, indices) {
|
|
872
|
+
if (!indices) {
|
|
873
|
+
indices = tree;
|
|
874
|
+
tree = this.getLayers();
|
|
875
|
+
}
|
|
876
|
+
let proofHashes = [];
|
|
877
|
+
let currentLayerIndices = indices;
|
|
878
|
+
for (const treeLayer of tree) {
|
|
879
|
+
const siblings = [];
|
|
880
|
+
for (const index of currentLayerIndices) {
|
|
881
|
+
if (index % 2 === 0) {
|
|
882
|
+
const idx = index + 1;
|
|
883
|
+
if (!currentLayerIndices.includes(idx)) {
|
|
884
|
+
if (treeLayer[idx]) {
|
|
885
|
+
siblings.push(treeLayer[idx]);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const idx = index - 1;
|
|
891
|
+
if (!currentLayerIndices.includes(idx)) {
|
|
892
|
+
if (treeLayer[idx]) {
|
|
893
|
+
siblings.push(treeLayer[idx]);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
proofHashes = proofHashes.concat(siblings);
|
|
899
|
+
const uniqueIndices = new Set();
|
|
900
|
+
for (const index of currentLayerIndices) {
|
|
901
|
+
if (index % 2 === 0) {
|
|
902
|
+
uniqueIndices.add(index / 2);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (index % 2 === 0) {
|
|
906
|
+
uniqueIndices.add((index + 1) / 2);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
uniqueIndices.add((index - 1) / 2);
|
|
910
|
+
}
|
|
911
|
+
currentLayerIndices = Array.from(uniqueIndices);
|
|
912
|
+
}
|
|
913
|
+
return proofHashes;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* getHexMultiProof
|
|
917
|
+
* @desc Returns the multiproof for given tree indices as hex strings.
|
|
918
|
+
* @param {Number[]} indices - Tree indices.
|
|
919
|
+
* @return {String[]} - Multiproofs as hex strings.
|
|
920
|
+
* @example
|
|
921
|
+
* ```js
|
|
922
|
+
*const indices = [2, 5, 6]
|
|
923
|
+
*const proof = tree.getHexMultiProof(indices)
|
|
924
|
+
*```
|
|
925
|
+
*/
|
|
926
|
+
getHexMultiProof(tree, indices) {
|
|
927
|
+
return this.getMultiProof(tree, indices).map((x) => this.bufferToHex(x));
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* getProofFlags
|
|
931
|
+
* @desc Returns list of booleans where proofs should be used instead of hashing.
|
|
932
|
+
* Proof flags are used in the Solidity multiproof verifiers.
|
|
933
|
+
* @param {Number[]|Buffer[]} leaves
|
|
934
|
+
* @param {Buffer[]} proofs
|
|
935
|
+
* @return {Boolean[]} - Boolean flags
|
|
936
|
+
* @example
|
|
937
|
+
* ```js
|
|
938
|
+
*const indices = [2, 5, 6]
|
|
939
|
+
*const proof = tree.getMultiProof(indices)
|
|
940
|
+
*const proofFlags = tree.getProofFlags(leaves, proof)
|
|
941
|
+
*```
|
|
942
|
+
*/
|
|
943
|
+
getProofFlags(leaves, proofs) {
|
|
944
|
+
if (!Array.isArray(leaves) || leaves.length <= 0) {
|
|
945
|
+
throw new Error('Invalid Inputs!');
|
|
946
|
+
}
|
|
947
|
+
let ids;
|
|
948
|
+
if (leaves.every(Number.isInteger)) {
|
|
949
|
+
ids = [...leaves].sort((a, b) => a === b ? 0 : a > b ? 1 : -1); // Indices where passed
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
ids = leaves.map((el) => this.bufferIndexOf(this.leaves, el, this.sortLeaves)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1);
|
|
953
|
+
}
|
|
954
|
+
if (!ids.every((idx) => idx !== -1)) {
|
|
955
|
+
throw new Error('Element does not exist in Merkle tree');
|
|
956
|
+
}
|
|
957
|
+
const proofBufs = proofs.map(item => this.bufferify(item));
|
|
958
|
+
const tested = [];
|
|
959
|
+
const flags = [];
|
|
960
|
+
for (let index = 0; index < this.layers.length; index++) {
|
|
961
|
+
const layer = this.layers[index];
|
|
962
|
+
ids = ids.reduce((ids, idx) => {
|
|
963
|
+
const skipped = tested.includes(layer[idx]);
|
|
964
|
+
if (!skipped) {
|
|
965
|
+
const pairElement = this.getPairNode(layer, idx);
|
|
966
|
+
const proofUsed = this.bufferArrayIncludes(proofBufs, layer[idx]) || this.bufferArrayIncludes(proofBufs, pairElement);
|
|
967
|
+
pairElement && flags.push(!proofUsed);
|
|
968
|
+
tested.push(layer[idx]);
|
|
969
|
+
tested.push(pairElement);
|
|
970
|
+
}
|
|
971
|
+
ids.push((idx / 2) | 0);
|
|
972
|
+
return ids;
|
|
973
|
+
}, []);
|
|
974
|
+
}
|
|
975
|
+
return flags;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* verify
|
|
979
|
+
* @desc Returns true if the proof path (array of hashes) can connect the target node
|
|
980
|
+
* to the Merkle root.
|
|
981
|
+
* @param {Object[]} proof - Array of proof objects that should connect
|
|
982
|
+
* target node to Merkle root.
|
|
983
|
+
* @param {Buffer} targetNode - Target node Buffer
|
|
984
|
+
* @param {Buffer} root - Merkle root Buffer
|
|
985
|
+
* @return {Boolean}
|
|
986
|
+
* @example
|
|
987
|
+
*```js
|
|
988
|
+
*const root = tree.getRoot()
|
|
989
|
+
*const proof = tree.getProof(leaves[2])
|
|
990
|
+
*const verified = tree.verify(proof, leaves[2], root)
|
|
991
|
+
*```
|
|
992
|
+
*/
|
|
993
|
+
verify(proof, targetNode, root) {
|
|
994
|
+
let hash = this.bufferify(targetNode);
|
|
995
|
+
root = this.bufferify(root);
|
|
996
|
+
if (!Array.isArray(proof) ||
|
|
997
|
+
!targetNode ||
|
|
998
|
+
!root) {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
for (let i = 0; i < proof.length; i++) {
|
|
1002
|
+
const node = proof[i];
|
|
1003
|
+
let data = null;
|
|
1004
|
+
let isLeftNode = null;
|
|
1005
|
+
// case for when proof is hex values only
|
|
1006
|
+
if (typeof node === 'string') {
|
|
1007
|
+
data = this.bufferify(node);
|
|
1008
|
+
isLeftNode = true;
|
|
1009
|
+
}
|
|
1010
|
+
else if (Array.isArray(node)) {
|
|
1011
|
+
isLeftNode = (node[0] === 0);
|
|
1012
|
+
data = this.bufferify(node[1]);
|
|
1013
|
+
}
|
|
1014
|
+
else if (buffer_1.Buffer.isBuffer(node)) {
|
|
1015
|
+
data = node;
|
|
1016
|
+
isLeftNode = true;
|
|
1017
|
+
}
|
|
1018
|
+
else if (node instanceof Object) {
|
|
1019
|
+
data = this.bufferify(node.data);
|
|
1020
|
+
isLeftNode = (node.position === 'left');
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
throw new Error('Expected node to be of type string or object');
|
|
1024
|
+
}
|
|
1025
|
+
const buffers = [];
|
|
1026
|
+
if (this.isBitcoinTree) {
|
|
1027
|
+
buffers.push(buffer_reverse_1.default(hash));
|
|
1028
|
+
buffers[isLeftNode ? 'unshift' : 'push'](buffer_reverse_1.default(data));
|
|
1029
|
+
hash = this.hashFn(this.concatenator(buffers));
|
|
1030
|
+
hash = buffer_reverse_1.default(this.hashFn(hash));
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
if (this.sortPairs) {
|
|
1034
|
+
if (buffer_1.Buffer.compare(hash, data) === -1) {
|
|
1035
|
+
buffers.push(hash, data);
|
|
1036
|
+
hash = this.hashFn(this.concatenator(buffers));
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
buffers.push(data, hash);
|
|
1040
|
+
hash = this.hashFn(this.concatenator(buffers));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
buffers.push(hash);
|
|
1045
|
+
buffers[isLeftNode ? 'unshift' : 'push'](data);
|
|
1046
|
+
hash = this.hashFn(this.concatenator(buffers));
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return buffer_1.Buffer.compare(hash, root) === 0;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* verifyMultiProof
|
|
1054
|
+
* @desc Returns true if the multiproofs can connect the leaves to the Merkle root.
|
|
1055
|
+
* @param {Buffer} root - Merkle tree root
|
|
1056
|
+
* @param {Number[]} proofIndices - Leave indices for proof
|
|
1057
|
+
* @param {Buffer[]} proofLeaves - Leaf values at indices for proof
|
|
1058
|
+
* @param {Number} leavesCount - Count of original leaves
|
|
1059
|
+
* @param {Buffer[]} proof - Multiproofs given indices
|
|
1060
|
+
* @return {Boolean}
|
|
1061
|
+
* @example
|
|
1062
|
+
*```js
|
|
1063
|
+
*const leaves = tree.getLeaves()
|
|
1064
|
+
*const root = tree.getRoot()
|
|
1065
|
+
*const treeFlat = tree.getLayersFlat()
|
|
1066
|
+
*const leavesCount = leaves.length
|
|
1067
|
+
*const proofIndices = [2, 5, 6]
|
|
1068
|
+
*const proofLeaves = proofIndices.map(i => leaves[i])
|
|
1069
|
+
*const proof = tree.getMultiProof(treeFlat, indices)
|
|
1070
|
+
*const verified = tree.verifyMultiProof(root, proofIndices, proofLeaves, leavesCount, proof)
|
|
1071
|
+
*```
|
|
1072
|
+
*/
|
|
1073
|
+
verifyMultiProof(root, proofIndices, proofLeaves, leavesCount, proof) {
|
|
1074
|
+
const isUneven = this.isUnevenTree();
|
|
1075
|
+
if (isUneven) {
|
|
1076
|
+
// TODO: combine these functions and simplify
|
|
1077
|
+
return this.verifyMultiProofForUnevenTree(root, proofIndices, proofLeaves, leavesCount, proof);
|
|
1078
|
+
}
|
|
1079
|
+
const depth = Math.ceil(Math.log2(leavesCount));
|
|
1080
|
+
root = this.bufferify(root);
|
|
1081
|
+
proofLeaves = proofLeaves.map(leaf => this.bufferify(leaf));
|
|
1082
|
+
proof = proof.map(leaf => this.bufferify(leaf));
|
|
1083
|
+
const tree = {};
|
|
1084
|
+
for (const [index, leaf] of this.zip(proofIndices, proofLeaves)) {
|
|
1085
|
+
tree[(Math.pow(2, depth)) + index] = leaf;
|
|
1086
|
+
}
|
|
1087
|
+
for (const [index, proofitem] of this.zip(this.getProofIndices(proofIndices, depth), proof)) {
|
|
1088
|
+
tree[index] = proofitem;
|
|
1089
|
+
}
|
|
1090
|
+
let indexqueue = Object.keys(tree).map(value => +value).sort((a, b) => a - b);
|
|
1091
|
+
indexqueue = indexqueue.slice(0, indexqueue.length - 1);
|
|
1092
|
+
let i = 0;
|
|
1093
|
+
while (i < indexqueue.length) {
|
|
1094
|
+
const index = indexqueue[i];
|
|
1095
|
+
if (index >= 2 && ({}).hasOwnProperty.call(tree, index ^ 1)) {
|
|
1096
|
+
let pair = [tree[index - (index % 2)], tree[index - (index % 2) + 1]];
|
|
1097
|
+
if (this.sortPairs) {
|
|
1098
|
+
pair = pair.sort(buffer_1.Buffer.compare);
|
|
1099
|
+
}
|
|
1100
|
+
const hash = pair[1] ? this.hashFn(this.concatenator(pair)) : pair[0];
|
|
1101
|
+
tree[(index / 2) | 0] = hash;
|
|
1102
|
+
indexqueue.push((index / 2) | 0);
|
|
1103
|
+
}
|
|
1104
|
+
i += 1;
|
|
1105
|
+
}
|
|
1106
|
+
return !proofIndices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root));
|
|
1107
|
+
}
|
|
1108
|
+
verifyMultiProofWithFlags(root, leaves, proofs, proofFlag) {
|
|
1109
|
+
root = this.bufferify(root);
|
|
1110
|
+
leaves = leaves.map(this.bufferify);
|
|
1111
|
+
proofs = proofs.map(this.bufferify);
|
|
1112
|
+
const leavesLen = leaves.length;
|
|
1113
|
+
const totalHashes = proofFlag.length;
|
|
1114
|
+
const hashes = [];
|
|
1115
|
+
let leafPos = 0;
|
|
1116
|
+
let hashPos = 0;
|
|
1117
|
+
let proofPos = 0;
|
|
1118
|
+
for (let i = 0; i < totalHashes; i++) {
|
|
1119
|
+
const bufA = proofFlag[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) : proofs[proofPos++];
|
|
1120
|
+
const bufB = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
|
|
1121
|
+
const buffers = [bufA, bufB].sort(buffer_1.Buffer.compare);
|
|
1122
|
+
hashes[i] = this.hashFn(this.concatenator(buffers));
|
|
1123
|
+
}
|
|
1124
|
+
return buffer_1.Buffer.compare(hashes[totalHashes - 1], root) === 0;
|
|
1125
|
+
}
|
|
1126
|
+
verifyMultiProofForUnevenTree(root, indices, leaves, leavesCount, proof) {
|
|
1127
|
+
root = this.bufferify(root);
|
|
1128
|
+
leaves = leaves.map(leaf => this.bufferify(leaf));
|
|
1129
|
+
proof = proof.map(leaf => this.bufferify(leaf));
|
|
1130
|
+
const computedRoot = this.calculateRootForUnevenTree(indices, leaves, leavesCount, proof);
|
|
1131
|
+
return root.equals(computedRoot);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* getDepth
|
|
1135
|
+
* @desc Returns the tree depth (number of layers)
|
|
1136
|
+
* @return {Number}
|
|
1137
|
+
* @example
|
|
1138
|
+
*```js
|
|
1139
|
+
*const depth = tree.getDepth()
|
|
1140
|
+
*```
|
|
1141
|
+
*/
|
|
1142
|
+
getDepth() {
|
|
1143
|
+
return this.getLayers().length - 1;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* getLayersAsObject
|
|
1147
|
+
* @desc Returns the layers as nested objects instead of an array.
|
|
1148
|
+
* @example
|
|
1149
|
+
*```js
|
|
1150
|
+
*const layersObj = tree.getLayersAsObject()
|
|
1151
|
+
*```
|
|
1152
|
+
*/
|
|
1153
|
+
getLayersAsObject() {
|
|
1154
|
+
const layers = this.getLayers().map((layer) => layer.map((value) => this.bufferToHex(value, false)));
|
|
1155
|
+
const objs = [];
|
|
1156
|
+
for (let i = 0; i < layers.length; i++) {
|
|
1157
|
+
const arr = [];
|
|
1158
|
+
for (let j = 0; j < layers[i].length; j++) {
|
|
1159
|
+
const obj = { [layers[i][j]]: null };
|
|
1160
|
+
if (objs.length) {
|
|
1161
|
+
obj[layers[i][j]] = {};
|
|
1162
|
+
const a = objs.shift();
|
|
1163
|
+
const akey = Object.keys(a)[0];
|
|
1164
|
+
obj[layers[i][j]][akey] = a[akey];
|
|
1165
|
+
if (objs.length) {
|
|
1166
|
+
const b = objs.shift();
|
|
1167
|
+
const bkey = Object.keys(b)[0];
|
|
1168
|
+
obj[layers[i][j]][bkey] = b[bkey];
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
arr.push(obj);
|
|
1172
|
+
}
|
|
1173
|
+
objs.push(...arr);
|
|
1174
|
+
}
|
|
1175
|
+
return objs[0];
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* verify
|
|
1179
|
+
* @desc Returns true if the proof path (array of hashes) can connect the target node
|
|
1180
|
+
* to the Merkle root.
|
|
1181
|
+
* @param {Object[]} proof - Array of proof objects that should connect
|
|
1182
|
+
* target node to Merkle root.
|
|
1183
|
+
* @param {Buffer} targetNode - Target node Buffer
|
|
1184
|
+
* @param {Buffer} root - Merkle root Buffer
|
|
1185
|
+
* @param {Function} hashFunction - Hash function for hashing leaves and nodes
|
|
1186
|
+
* @param {Object} options - Additional options
|
|
1187
|
+
* @return {Boolean}
|
|
1188
|
+
* @example
|
|
1189
|
+
*```js
|
|
1190
|
+
*const verified = MerkleTree.verify(proof, leaf, root, sha256, options)
|
|
1191
|
+
*```
|
|
1192
|
+
*/
|
|
1193
|
+
static verify(proof, targetNode, root, hashFn = sha256_1.default, options = {}) {
|
|
1194
|
+
const tree = new MerkleTree([], hashFn, options);
|
|
1195
|
+
return tree.verify(proof, targetNode, root);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* getMultiProof
|
|
1199
|
+
* @desc Returns the multiproof for given tree indices.
|
|
1200
|
+
* @param {Buffer[]} tree - Tree as a flat array.
|
|
1201
|
+
* @param {Number[]} indices - Tree indices.
|
|
1202
|
+
* @return {Buffer[]} - Multiproofs
|
|
1203
|
+
*
|
|
1204
|
+
*@example
|
|
1205
|
+
* ```js
|
|
1206
|
+
*const flatTree = tree.getLayersFlat()
|
|
1207
|
+
*const indices = [2, 5, 6]
|
|
1208
|
+
*const proof = MerkleTree.getMultiProof(flatTree, indices)
|
|
1209
|
+
*```
|
|
1210
|
+
*/
|
|
1211
|
+
static getMultiProof(tree, indices) {
|
|
1212
|
+
const t = new MerkleTree([]);
|
|
1213
|
+
return t.getMultiProof(tree, indices);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* resetTree
|
|
1217
|
+
* @desc Resets the tree by clearing the leaves and layers.
|
|
1218
|
+
* @example
|
|
1219
|
+
*```js
|
|
1220
|
+
*tree.resetTree()
|
|
1221
|
+
*```
|
|
1222
|
+
*/
|
|
1223
|
+
resetTree() {
|
|
1224
|
+
this.leaves = [];
|
|
1225
|
+
this.layers = [];
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* getPairNode
|
|
1229
|
+
* @desc Returns the node at the index for given layer.
|
|
1230
|
+
* @param {Buffer[]} layer - Tree layer
|
|
1231
|
+
* @param {Number} index - Index at layer.
|
|
1232
|
+
* @return {Buffer} - Node
|
|
1233
|
+
*
|
|
1234
|
+
*@example
|
|
1235
|
+
* ```js
|
|
1236
|
+
*const node = tree.getPairNode(layer, index)
|
|
1237
|
+
*```
|
|
1238
|
+
*/
|
|
1239
|
+
getPairNode(layer, idx) {
|
|
1240
|
+
const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
|
|
1241
|
+
if (pairIdx < layer.length) {
|
|
1242
|
+
return layer[pairIdx];
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* toTreeString
|
|
1250
|
+
* @desc Returns a visual representation of the merkle tree as a string.
|
|
1251
|
+
* @return {String}
|
|
1252
|
+
* @example
|
|
1253
|
+
*```js
|
|
1254
|
+
*console.log(tree.toTreeString())
|
|
1255
|
+
*```
|
|
1256
|
+
*/
|
|
1257
|
+
toTreeString() {
|
|
1258
|
+
const obj = this.getLayersAsObject();
|
|
1259
|
+
return treeify_1.default.asTree(obj, true);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* toString
|
|
1263
|
+
* @desc Returns a visual representation of the merkle tree as a string.
|
|
1264
|
+
* @example
|
|
1265
|
+
*```js
|
|
1266
|
+
*console.log(tree.toString())
|
|
1267
|
+
*```
|
|
1268
|
+
*/
|
|
1269
|
+
toString() {
|
|
1270
|
+
return this.toTreeString();
|
|
1271
|
+
}
|
|
1272
|
+
isUnevenTree(treeLayers) {
|
|
1273
|
+
const depth = (treeLayers === null || treeLayers === void 0 ? void 0 : treeLayers.length) || this.getDepth();
|
|
1274
|
+
return !this.isPowOf2(depth);
|
|
1275
|
+
}
|
|
1276
|
+
isPowOf2(v) {
|
|
1277
|
+
return v && !(v & (v - 1));
|
|
1278
|
+
}
|
|
1279
|
+
isValidLeafIndex(idx) {
|
|
1280
|
+
return idx >= 0 && idx < this.getLeafCount();
|
|
1281
|
+
}
|
|
1282
|
+
calculateRootForUnevenTree(leafIndices, leafHashes, totalLeavesCount, proofHashes) {
|
|
1283
|
+
const leafTuples = this.zip(leafIndices, leafHashes).sort(([indexA], [indexB]) => indexA - indexB);
|
|
1284
|
+
const leafTupleIndices = leafTuples.map(([index]) => index);
|
|
1285
|
+
const proofIndices = this.getProofIndicesForUnevenTree(leafTupleIndices, totalLeavesCount);
|
|
1286
|
+
let nextSliceStart = 0;
|
|
1287
|
+
const proofTuplesByLayers = [];
|
|
1288
|
+
for (let i = 0; i < proofIndices.length; i++) {
|
|
1289
|
+
const indices = proofIndices[i];
|
|
1290
|
+
const sliceStart = nextSliceStart;
|
|
1291
|
+
nextSliceStart += indices.length;
|
|
1292
|
+
proofTuplesByLayers[i] = this.zip(indices, proofHashes.slice(sliceStart, nextSliceStart));
|
|
1293
|
+
}
|
|
1294
|
+
const tree = [leafTuples];
|
|
1295
|
+
for (let layerIndex = 0; layerIndex < proofTuplesByLayers.length; layerIndex++) {
|
|
1296
|
+
const currentLayer = proofTuplesByLayers[layerIndex].concat(tree[layerIndex]).sort(([indexA], [indexB]) => indexA - indexB)
|
|
1297
|
+
.map(([, hash]) => hash);
|
|
1298
|
+
const s = tree[layerIndex].map(([layerIndex]) => layerIndex);
|
|
1299
|
+
const parentIndices = [...new Set(s.map((index) => {
|
|
1300
|
+
if (index % 2 === 0) {
|
|
1301
|
+
return index / 2;
|
|
1302
|
+
}
|
|
1303
|
+
if (index % 2 === 0) {
|
|
1304
|
+
return (index + 1) / 2;
|
|
1305
|
+
}
|
|
1306
|
+
return (index - 1) / 2;
|
|
1307
|
+
}))];
|
|
1308
|
+
const parentLayer = [];
|
|
1309
|
+
for (let i = 0; i < parentIndices.length; i++) {
|
|
1310
|
+
const parentNodeTreeIndex = parentIndices[i];
|
|
1311
|
+
const bufA = currentLayer[i * 2];
|
|
1312
|
+
const bufB = currentLayer[i * 2 + 1];
|
|
1313
|
+
const hash = bufB ? this.hashFn(this.concatenator([bufA, bufB])) : bufA;
|
|
1314
|
+
parentLayer.push([parentNodeTreeIndex, hash]);
|
|
1315
|
+
}
|
|
1316
|
+
tree.push(parentLayer);
|
|
1317
|
+
}
|
|
1318
|
+
return tree[tree.length - 1][0][1];
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
exports.MerkleTree = MerkleTree;
|
|
1322
|
+
if (typeof window !== 'undefined') {
|
|
1323
|
+
;
|
|
1324
|
+
window.MerkleTree = MerkleTree;
|
|
1325
|
+
}
|
|
1326
|
+
exports.default = MerkleTree;
|