merkle
Version:
Javascript implementation of merkle trees
196 lines (173 loc) • 4.2 kB
JavaScript
var crypto = require('crypto');
var through = require('through');
var REGEXP = {
'md5': "^[0-9a-f]{32}$",
'sha1': "^[0-9a-f]{40}$",
'ripemd160': "^[0-9a-f]{40}$",
'sha256': "^[0-9a-f]{64}$",
'sha512': "^[0-9a-f]{128}$",
'whirlpool': "^[0-9a-f]{128}$",
'DEFAULT': "^$"
};
function Merkle (hashFunc, hashFuncName, useUpperCaseForHash) {
var that = this;
var resFunc = function () {
return root();
};
var regexpStr = REGEXP[hashFuncName] || REGEXP.DEFAULT;
if (useUpperCaseForHash) {
// Use only capital letters if upper case is enabled
regexpStr = regexpStr.replace('a', 'A').replace('f', 'F');
}
that.hashResultRegexp = new RegExp(regexpStr);
that.leaves = [];
that.treeDepth = 0;
that.rows = [];
that.nodesCount = 0;
function feed(anyData) {
if(anyData && anyData.match(that.hashResultRegexp)){
// Push leaf without hashing it since it is already a hash
that.leaves.push(anyData);
}
else{
var hash = hashFunc(anyData);
if (useUpperCaseForHash) {
hash = hash.toUpperCase();
}
that.leaves.push(hash);
}
return that;
}
function depth() {
// Compute tree depth
if(!that.treeDepth){
var pow = 0;
while(Math.pow(2, pow) < that.leaves.length){
pow++;
}
that.treeDepth = pow;
}
return that.treeDepth;
}
function levels() {
return depth() + 1;
}
function nodes() {
return that.nodesCount;
}
function root() {
return that.rows[0][0];
}
function level(i) {
return that.rows[i];
}
function compute() {
var theDepth = depth();
if(that.rows.length == 0){
// Compute the nodes of each level
for (var i = 0; i < theDepth; i++) {
that.rows.push([]);
}
that.rows[theDepth] = that.leaves;
for (var j = theDepth-1; j >= 0; j--) {
that.rows[j] = getNodes(that.rows[j+1]);
that.nodesCount += that.rows[j].length;
}
}
}
function getNodes(leaves) {
var remainder = leaves.length % 2;
var nodes = [];
var hash;
for (var i = 0; i < leaves.length - 1; i = i + 2) {
hash = hashFunc(leaves[i] + leaves[i+1]);
if (useUpperCaseForHash) {
hash = hash.toUpperCase();
}
nodes[i/2] = hash;
}
if(remainder === 1){
nodes[((leaves.length-remainder)/2)] = leaves[leaves.length - 1];
}
return nodes;
}
// PUBLIC
/**
* Return the stream, with resulting stream begin root hash string.
**/
var stream = through(
function write (data) {
feed('' + data);
},
function end () {
compute();
this.emit('data', resFunc());
this.emit('end');
});
/**
* Return the stream, but resulting stream will be json.
**/
stream.json = function () {
resFunc = function() {
return {
root: root(),
level: level(),
depth: depth(),
levels: levels(),
nodes: nodes()
};
};
return this;
};
/**
* Computes merkle tree synchronously, returning json result.
**/
stream.sync = function (leaves) {
leaves.forEach(function(leaf){
feed(leaf);
});
compute();
resFunc = function() {
return {
root: root,
level: level,
depth: depth,
levels: levels,
nodes: nodes
};
};
return resFunc();
};
/**
* Computes merkle tree asynchronously, returning json as callback result.
**/
stream.async = function (leaves, done) {
leaves.forEach(function(leaf){
feed(leaf);
});
compute();
resFunc = function() {
return {
root: root,
level: level,
depth: depth,
levels: levels,
nodes: nodes
};
};
done(null, resFunc());
};
return stream;
}
module.exports = function (hashFuncName, useUpperCaseForHash) {
return new Merkle(function (input) {
if (hashFuncName === 'none') {
return input;
} else {
var hash = crypto.createHash(hashFuncName);
return hash.update(input).digest('hex');
}
}, hashFuncName,
// Use upper case y default
useUpperCaseForHash !== false);
};