UNPKG

merkle-tree-lib

Version:

Merkle Tree implementation with BIP340 tagged hash support.

162 lines (161 loc) 6.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MerkleTree = void 0; const crypto = __importStar(require("crypto")); /** * MerkleTree class: builds a binary Merkle tree using tagged hashing (BIP-0340). * It can compute the Merkle root and generate proofs for given leaves. */ class MerkleTree { /** * Initialize the Merkle tree with an array of data (strings) and hashing tags. * @param leavesData - Array of strings representing leaf data. * @param leafTag - Tag for hashing leaves (context-specific tag). Defaults to "Bitcoin_Transaction". * @param branchTag - Tag for hashing internal nodes. Defaults to the same as leafTag. */ constructor(leavesData, leafTag = "Bitcoin_Transaction", branchTag = "Bitcoin_Transaction") { this.root = Buffer.alloc(0); // Initialize to avoid TS error this.tagHashLeaf = this.computeTagHash(leafTag); this.tagHashBranch = this.computeTagHash(branchTag); // Compute all leaf hashes this.leafHashes = leavesData.map(data => { const msg = Buffer.from(data, 'utf8'); return this.taggedHash(this.tagHashLeaf, msg); }); // Build Merkle tree levels this.levels = []; this.buildTree(); } /** Compute SHA256 hash of the tag (as UTF-8), used for tagged hashing. */ computeTagHash(tag) { return crypto.createHash('sha256').update(tag, 'utf8').digest(); } /** * Compute a tagged hash: SHA256( tagHash || tagHash || message ). * @param tagHash - Buffer of 32-byte hash of the tag. * @param msg - Message bytes to hash. * @returns 32-byte SHA256 digest. */ taggedHash(tagHash, msg) { // Use two tagHash copies as prefix, then the message return crypto.createHash('sha256') .update(tagHash) .update(tagHash) .update(msg) .digest(); } /** Build the Merkle tree levels and root from the leaf hashes. */ buildTree() { let levelNodes = this.leafHashes.slice(); // copy leaf hashes const n = levelNodes.length; if (n === 0) { // No leaves: define root as empty buffer this.root = Buffer.alloc(0); this.levels = []; return; } // If there's only one leaf, that leaf's hash will be the root (no pairing needed). // Otherwise, build the tree by hashing pairs of nodes up to the root. let levelIndex = 0; while (levelNodes.length > 1) { // If odd number of nodes, duplicate the last node to make it even if (levelNodes.length % 2 === 1) { levelNodes.push(levelNodes[levelNodes.length - 1]); } // Store the current level (after padding) this.levels[levelIndex] = levelNodes; // Compute parent level const nextLevel = []; for (let i = 0; i < levelNodes.length; i += 2) { const left = levelNodes[i]; const right = levelNodes[i + 1]; // Branch hash using the branch tag const parentHash = this.taggedHash(this.tagHashBranch, Buffer.concat([left, right])); nextLevel.push(parentHash); } levelNodes = nextLevel; levelIndex++; } // The loop ends when levelNodes has a single element (root) this.levels[levelIndex] = levelNodes; this.root = levelNodes[0]; } /** * Get the Merkle root as a hex string. * @returns Hexadecimal string of the Merkle root. */ getHexRoot() { return this.root.toString('hex'); } /** * Generate the Merkle proof for the leaf at the given index. * @param index - Index of the target leaf in the original input array. * @returns An array of proof elements, each a tuple [siblingHash (Buffer), side (0 or 1)]. * side = 0 indicates the sibling is the left node, side = 1 indicates the sibling is the right node. */ getProof(index) { const proof = []; if (index < 0 || index >= this.leafHashes.length) { return proof; // index out of range, return empty proof } // Traverse from leaf level up to root (levels[0] to levels[h-1], where levels[h] is root) let currentIndex = index; for (let level = 0; level < this.levels.length - 1; level++) { const levelNodes = this.levels[level]; const isEvenIndex = (currentIndex % 2 === 0); let siblingIndex; let side; if (isEvenIndex) { // Current node is a left child, sibling is to the right siblingIndex = currentIndex + 1; // In case of a duplicated last node, siblingIndex may equal currentIndex // (but our building algorithm pads such that sibling always exists) side = 1; // sibling is on the right side } else { // Current node is a right child, sibling is to the left siblingIndex = currentIndex - 1; side = 0; // sibling is on the left side } const siblingHash = levelNodes[siblingIndex]; proof.push({ siblingHash, side }); // Move to the parent index for next level currentIndex = Math.floor(currentIndex / 2); } return proof; } } exports.MerkleTree = MerkleTree;