-
Notifications
You must be signed in to change notification settings - Fork 37
/
merkletools.js
252 lines (220 loc) · 8.92 KB
/
merkletools.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
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
/* Copyright 2017 Tierion
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var sha3512 = require('js-sha3').sha3_512
var sha3384 = require('js-sha3').sha3_384
var sha3256 = require('js-sha3').sha3_256
var sha3224 = require('js-sha3').sha3_224
var crypto = require('crypto')
var MerkleTools = function (treeOptions) {
// in case 'new' was omitted
if (!(this instanceof MerkleTools)) {
return new MerkleTools(treeOptions)
}
var hashType = 'sha256'
if (treeOptions) { // if tree options were supplied, then process them
if (treeOptions.hashType !== undefined) { // set the hash function to the user's choice
hashType = treeOptions.hashType
}
}
var hashFunction = function (value) {
switch (hashType) {
case 'SHA3-224':
return Buffer.from(sha3224.array(value))
case 'SHA3-256':
return Buffer.from(sha3256.array(value))
case 'SHA3-384':
return Buffer.from(sha3384.array(value))
case 'SHA3-512':
return Buffer.from(sha3512.array(value))
default:
return crypto.createHash(hashType).update(value).digest()
}
}
var tree = {}
tree.leaves = []
tree.levels = []
tree.isReady = false
/// /////////////////////////////////////////
// Public Primary functions
/// /////////////////////////////////////////
// Resets the current tree to empty
this.resetTree = function () {
tree = {}
tree.leaves = []
tree.levels = []
tree.isReady = false
}
// Add a leaf to the tree
// Accepts hash value as a Buffer or hex string
this.addLeaf = function (value, doHash) {
tree.isReady = false
if (doHash) value = hashFunction(value)
tree.leaves.push(_getBuffer(value))
}
// Add a leaves to the tree
// Accepts hash values as an array of Buffers or hex strings
this.addLeaves = function (valuesArray, doHash) {
tree.isReady = false
valuesArray.forEach(function (value) {
if (doHash) value = hashFunction(value)
tree.leaves.push(_getBuffer(value))
})
}
// Returns a leaf at the given index
this.getLeaf = function (index) {
if (index < 0 || index > tree.leaves.length - 1) return null // index is out of array bounds
return tree.leaves[index]
}
// Returns the number of leaves added to the tree
this.getLeafCount = function () {
return tree.leaves.length
}
// Returns the ready state of the tree
this.getTreeReadyState = function () {
return tree.isReady
}
// Generates the merkle tree
this.makeTree = function (doubleHash) {
tree.isReady = false
var leafCount = tree.leaves.length
if (leafCount > 0) { // skip this whole process if there are no leaves added to the tree
tree.levels = []
tree.levels.unshift(tree.leaves)
while (tree.levels[0].length > 1) {
tree.levels.unshift(_calculateNextLevel(doubleHash))
}
}
tree.isReady = true
}
// Generates a Bitcoin style merkle tree
this.makeBTCTree = function (doubleHash) {
tree.isReady = false
var leafCount = tree.leaves.length
if (leafCount > 0) { // skip this whole process if there are no leaves added to the tree
tree.levels = []
tree.levels.unshift(tree.leaves)
while (tree.levels[0].length > 1) {
tree.levels.unshift(_calculateBTCNextLevel(doubleHash))
}
}
tree.isReady = true
}
// Returns the merkle root value for the tree
this.getMerkleRoot = function () {
if (!tree.isReady || tree.levels.length === 0) return null
return tree.levels[0][0]
}
// Returns the proof for a leaf at the given index as an array of merkle siblings in hex format
this.getProof = function (index, asBinary) {
if (!tree.isReady) return null
var currentRowIndex = tree.levels.length - 1
if (index < 0 || index > tree.levels[currentRowIndex].length - 1) return null // the index it out of the bounds of the leaf array
var proof = []
for (var x = currentRowIndex; x > 0; x--) {
var currentLevelNodeCount = tree.levels[x].length
// skip if this is an odd end node
if (index === currentLevelNodeCount - 1 && currentLevelNodeCount % 2 === 1) {
index = Math.floor(index / 2)
continue
}
// determine the sibling for the current index and get its value
var isRightNode = index % 2
var siblingIndex = isRightNode ? (index - 1) : (index + 1)
if (asBinary) {
proof.push(Buffer.from(isRightNode ? [0x00] : [0x01]))
proof.push(tree.levels[x][siblingIndex])
} else {
var sibling = {}
var siblingPosition = isRightNode ? 'left' : 'right'
var siblingValue = tree.levels[x][siblingIndex].toString('hex')
sibling[siblingPosition] = siblingValue
proof.push(sibling)
}
index = Math.floor(index / 2) // set index to the parent index
}
return proof
}
// Takes a proof array, a target hash value, and a merkle root
// Checks the validity of the proof and return true or false
this.validateProof = function (proof, targetHash, merkleRoot, doubleHash) {
targetHash = _getBuffer(targetHash)
merkleRoot = _getBuffer(merkleRoot)
if (proof.length === 0) return targetHash.toString('hex') === merkleRoot.toString('hex') // no siblings, single item tree, so the hash should also be the root
var proofHash = targetHash
for (var x = 0; x < proof.length; x++) {
if (proof[x].left) { // then the sibling is a left node
if (doubleHash) { proofHash = hashFunction(hashFunction(Buffer.concat([_getBuffer(proof[x].left), proofHash]))) } else { proofHash = hashFunction(Buffer.concat([_getBuffer(proof[x].left), proofHash])) }
} else if (proof[x].right) { // then the sibling is a right node
if (doubleHash) { proofHash = hashFunction(hashFunction(Buffer.concat([proofHash, _getBuffer(proof[x].right)]))) } else { proofHash = hashFunction(Buffer.concat([proofHash, _getBuffer(proof[x].right)])) }
} else { // no left or right designation exists, proof is invalid
return false
}
}
return proofHash.toString('hex') === merkleRoot.toString('hex')
}
/// ///////////////////////////////////////
// Private Utility functions
/// ///////////////////////////////////////
// Internally, trees are made of nodes containing Buffer values only
// This helps ensure that leaves being added are Buffers, and will convert hex to Buffer if needed
function _getBuffer (value) {
if (value instanceof Buffer) { // we already have a buffer, so return it
return value
} else if (_isHex(value)) { // the value is a hex string, convert to buffer and return
return Buffer.from(value, 'hex')
} else { // the value is neither buffer nor hex string, will not process this, throw error
throw new Error("Bad hex value - '" + value + "'")
}
}
function _isHex (value) {
var hexRegex = /^[0-9A-Fa-f]{2,}$/
return hexRegex.test(value)
}
// Calculates the next level of node when building the merkle tree
// These values are calcalated off of the current highest level, level 0 and will be prepended to the levels array
function _calculateNextLevel (doubleHash) {
var nodes = []
var topLevel = tree.levels[0]
var topLevelCount = topLevel.length
for (var x = 0; x < topLevelCount; x += 2) {
if (x + 1 <= topLevelCount - 1) { // concatenate and hash the pair, add to the next level array, doubleHash if requested
if (doubleHash) {
nodes.push(hashFunction(hashFunction(Buffer.concat([topLevel[x], topLevel[x + 1]]))))
} else {
nodes.push(hashFunction(Buffer.concat([topLevel[x], topLevel[x + 1]])))
}
} else { // this is an odd ending node, promote up to the next level by itself
nodes.push(topLevel[x])
}
}
return nodes
}
// This version uses the BTC method of duplicating the odd ending nodes
function _calculateBTCNextLevel (doubleHash) {
var nodes = []
var topLevel = tree.levels[0]
var topLevelCount = topLevel.length
if (topLevelCount % 2 === 1) { // there is an odd count, duplicate the last element
topLevel.push(topLevel[topLevelCount - 1])
}
for (var x = 0; x < topLevelCount; x += 2) {
// concatenate and hash the pair, add to the next level array, doubleHash if requested
if (doubleHash) {
nodes.push(hashFunction(hashFunction(Buffer.concat([topLevel[x], topLevel[x + 1]]))))
} else {
nodes.push(hashFunction(Buffer.concat([topLevel[x], topLevel[x + 1]])))
}
}
return nodes
}
}
module.exports = MerkleTools