-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathmerkle.js
101 lines (86 loc) · 3.38 KB
/
merkle.js
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
'use strict';
module.exports = generate;
var crypto = require('crypto'),
async = require('async');
/*--------------------------------------------------------------------------------*/
/** Generates a merkle root from an array of hash leaves
* @param {object} array - The array of hash leaves
* @param {object} [options]
* @param {boolean} [options.reverse=true] - Indicates the leaves and root hashes should be reversed (endianness)
* @param {function} callback(err, merkleObject)
*/
function generate(array, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
/* Check if the hashes should be reversed for proper endianness */
if (options.reverse === false) {
recursiveMerkle(array, callback);
} else {
reverseHashes(array, function (err, reversedHashes) {
if (err) {
return callback(err);
}
recursiveMerkle(reversedHashes, function (err, merkle) {
if (err) {
return callback(err);
}
merkle.root = merkle.root.match(/.{2}/g).reverse().join('');
callback(null, merkle);
});
});
}
}
/** Recursively computes the nodes and root of a merkle tree
* @param {object} hashes - An array of hash leaves
* @param {function} callback - Invoked when the tree is computed
*/
function recursiveMerkle(hashes, callback) {
var concatHashes = [], merkleTree = {};
/* Duplicate last element if the array length is odd */
if (hashes.length % 2 === 1) {
hashes.push(hashes[hashes.length - 1]);
}
/* Concatenate hashes and push them to a new array */
for (var i = 0, length = hashes.length; i < length; i += 2) {
concatHashes.push(hashes[i] + hashes[i + 1]);
}
/* Map every element of the new array with a hash function */
async.map(concatHashes, function (data, callback) {
var hash = doubleHash(data);
callback(null, hash);
}, function (err, newHashes) {
if (err) {
return callback(err);
}
/* Check if the root has not been reached yet */
if (newHashes.length > 1) {
recursiveMerkle(newHashes, callback);
} else {
merkleTree.root = newHashes[0] || ' ';
callback(null, merkleTree);
}
});
}
/*--------------------------------------------------------------------------------*/
/** Reverses the bytes of each element of an array of hashes
* @param {object} hashes - The array of hashes whose elements must be reversed
* @param {function} whenReversed(err, reversedHashes) - A function to invoke when the operation is completed
*/
function reverseHashes(hashes, whenReversed) {
var reversedHashes = async.map(hashes, function (element, callback) {
callback(null, element.match(/.{2}/g).reverse().join(''));
}, whenReversed);
}
/** Hashes the input data twice
* @param {string} data - The data to be double hashed
* @param {string} [algorithm=sha256] - The hashing algorithm to be used
* @returns {string} hash2 - The double hash of the input data
*/
function doubleHash(data, algorithm) {
algorithm = algorithm || 'sha256';
var hash1 = crypto.createHash(algorithm).update(new Buffer(data, 'hex')).digest();
var hash2 = crypto.createHash(algorithm).update(hash1).digest('hex');
return hash2;
}