UNPKG

@yipsec/merked

Version:

a directed acyclic graph using merkle trees

161 lines (131 loc) 3.38 kB
/** * A hash tree or Merkle tree is a tree in which every "leaf" node is labelled * with the cryptographic hash of a data block, and every node that is not a * leaf (called a branch, inner node, or inode) is labelled with the * cryptographic hash of the labels of its child nodes. A hash tree allows * efficient and secure verification of the contents of a large data structure. * A hash tree is a generalization of a hash list and a hash chain. * @module merked/tree */ 'use strict'; const assert = require('node:assert'); const crypto = require('node:crypto'); class MerkleTree { /** * Implements a merkle hash tree * @constructor * @param {string[]|Buffer[]} leaves - Initial tree input * @param {Function} hasher - Hash function for building tree */ constructor(leaves, hasher) { this._hasher = hasher || MerkleTree.DEFAULT_HASH_FUNC; this._leaves = []; this._depth = 0; this._rows = []; this._count = 0; assert(Array.isArray(leaves), 'Invalid leaves array supplied'); assert(typeof this._hasher === 'function', 'Invalid hash function supplied'); for (var i = 0; i < leaves.length; i++) { this._feed(leaves[i]); } this._compute(); } /** * Computes the tree depth * @returns {Number} */ depth() { if (!this._depth) { var pow = 0; while (Math.pow(2, pow) < this._leaves.length){ pow++; } this._depth = pow; } return this._depth; } /** * Returns the number of levels in the tree * @returns {Number} */ levels() { return this.depth() + 1; } /** * Returns the number of nodes in the tree * @returns {Number} */ nodes() { return this._count; } /** * Returns the merkle root of the tree * @returns {String} */ root() { return this._rows[0][0]; } /** * Returns the leaves at the given level * @param {Number} level * @returns {String} */ level(level) { return this._rows[level]; } /** * Inserts leaf into bottom of tree * @private * @param {String} data * @returns {MerkleTree} */ _feed(data) { this._leaves.push(data); return this; } /** * Builds the tree from the leaves * @private */ _compute() { var depth = this.depth(); if (this._rows.length === 0) { for (var i = 0; i < depth; i++) { this._rows.push([]); } this._rows[depth] = this._leaves; for (var j = depth - 1; j >= 0; j--) { this._rows[j] = this._getNodes(this._rows[j + 1]); this._count += this._rows[j].length; } } } /** * Returns the nodes derived from the given leaves * @private * @param {Array} leaves * @returns {Array} */ _getNodes(leaves) { var remainder = leaves.length % 2; var nodes = []; for (var i = 0; i < leaves.length - 1; i = i + 2) { nodes[i / 2] = this._hasher(Buffer.concat([leaves[i], leaves[i + 1]])); } if (remainder === 1){ nodes[(leaves.length - remainder) / 2] = leaves[leaves.length - 1]; } return nodes; } /** * Default hash function (SHA-256) * @private * @param {String} input * @returns {String} */ static DEFAULT_HASH_FUNC(input) { return crypto.createHash('sha256').update(input).digest(); } } module.exports.MerkleTree = MerkleTree;