merkle-tools-sha3
Version:
Tools for creating merkle trees, generating merkle proofs, and verification of merkle proofs. Added SHA3 Keccak support. Forked from https://github.com/Tierion/merkle-tools
196 lines (165 loc) • 7.29 kB
JavaScript
/*jslint node: true */
;
var crypto = require('crypto');
var sha3_512 = require('js-sha3').sha3_512;
var MerkleTools = function (treeOptions) {
// in case 'new' was omitted
if (!(this instanceof MerkleTools)) {
return new MerkleTools(treeOptions);
}
// SHA3-512 (Latest) by default
var hashType = 'sha3-512';
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) {
// TODO add the rest of the sha3 family, for now only SHA3-512.
if (hashType == 'sha3-512') {
return new Buffer(sha3_512(value), "hex");
} else {
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) {
var leafLevelIndex = tree.levels.length - 1;
if (index < 0 || index > tree.levels[leafLevelIndex].length - 1) return null; // index is out of array bounds
return tree.levels[leafLevelIndex][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 () {
tree.isReady = false;
var leafCount = tree.leaves.length;
if (leafCount > 0) { // skip this whole process if there are no leaves added to the tree
var levelCount = Math.ceil(Math.log2(leafCount)) + 1;
tree.levels.unshift(tree.leaves);
while (tree.levels[0].length > 1) {
tree.levels.unshift(_calculateNextLevel());
}
}
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) {
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);
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) {
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
proofHash = hashFunction(Buffer.concat([_getBuffer(proof[x].left), proofHash]));
} else if (proof[x].right) { // then the sibling is a right node
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 new Buffer(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() {
var nodes = [];
var topLevel = tree.levels[0];
var topLevelCount = topLevel.length;
for (var x = 0; x < topLevelCount; x += 2) {
if (x + 1 <= topLevelCount - 1) { // concatonate and hash the pair, add to the next level array
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;
}
};
module.exports = MerkleTools;