commit 3606036e3a44aa46e5a16cc6d7beea8e49dec8ef Author: Jorge Zaccaro Date: Thu Jul 16 21:39:57 2015 -0500 First commit diff --git a/merkle.js b/merkle.js new file mode 100644 index 0000000..2cb52d8 --- /dev/null +++ b/merkle.js @@ -0,0 +1,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; +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..932ee59 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "main": "merkle.js", + "name": "merkle-bitcoin", + "version": "1.0.0", + "description": "Bitcoin-style merkle root generation.", + "homepage": "https://github.com/jorgezaccaro/merkle-bitcoin", + "repository": { + "type": "git", + "url": "git://github.com/jorgezaccaro/merkle-bitcoin.git" + }, + "keywords": [ + "merkle", + "bitcoin" + ], + "author": { + "name": "Jorge Zaccaro", + "email": "jorgezaccaro@gmail.com" + }, + "dependencies": { + "async": "^1.2.1" + }, + "license": "MIT" +} \ No newline at end of file