UNPKG

@variablesoftware/ts-merkle

Version:

🌳🔗🛡️ A TypeScript library for creating and verifying Merkle trees.

132 lines (131 loc) 4.2 kB
// src/index.ts import { createHash } from 'crypto'; /** * Compares two Uint8Array values lexicographically. * @param a First Uint8Array * @param b Second Uint8Array * @returns Negative if a < b, positive if a > b, 0 if equal */ function compareUint8(a, b) { if (a.length !== b.length) return a.length - b.length; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return a[i] - b[i]; } return 0; } /** * Hashes a pair of Uint8Array values using SHA-256. * @param a First value * @param b Second value * @returns The hash of the concatenated values */ function hashPair(a, b) { const hash = createHash('sha256'); hash.update(a); hash.update(b); return new Uint8Array(hash.digest()); } /** * Compute the Merkle root for a list of leaves. * @param leaves Array of leaf hashes (Uint8Array) * @param options Options for sorting and padding * @returns The Merkle root hash as a Uint8Array * @throws If leaves array is empty */ export function computeMerkleRoot(leaves, options) { if (leaves.length === 0) throw new Error('No leaves'); const sort = options?.sort !== false; const pad = options?.pad !== false; let nodes = leaves.slice(); while (nodes.length > 1) { const next = []; for (let i = 0; i < nodes.length; i += 2) { let left = nodes[i]; let right = nodes[i + 1]; if (right === undefined) { if (pad) right = left; else { next.push(left); continue; } } if (sort && compareUint8(left, right) > 0) [left, right] = [right, left]; next.push(hashPair(left, right)); } nodes = next; } return nodes[0]; } /** * Generate a Merkle proof for a given leaf index. * @param leaves Array of leaf hashes (Uint8Array) * @param index Index of the leaf to prove * @param options Options for sorting and padding * @returns Array of ProofNode objects * @throws If index is out of bounds */ export function computeMerkleProof(leaves, index, options) { if (index < 0 || index >= leaves.length) throw new Error('Invalid leaf index'); const sort = options?.sort !== false; const pad = options?.pad !== false; let nodes = leaves.slice(); let idx = index; const proof = []; while (nodes.length > 1) { const next = []; for (let i = 0; i < nodes.length; i += 2) { let left = nodes[i]; let right = nodes[i + 1]; if (right === undefined) { if (pad) right = left; else { next.push(left); continue; } } let pair = [left, right]; if (sort && compareUint8(left, right) > 0) pair = [right, left]; // If our index is in this pair, record the sibling if (i === idx || i + 1 === idx) { const isLeft = idx % 2 === 0; const sibling = isLeft ? right : left; proof.push({ sibling, position: isLeft ? 'right' : 'left' }); idx = Math.floor(i / 2); } next.push(hashPair(pair[0], pair[1])); } nodes = next; } return proof; } /** * Verify that a given leaf + proof yields the expected root. * @param leaf The leaf hash * @param proof Array of sibling nodes (ProofNode[]) * @param root The expected Merkle root * @param options Options for sorting * @returns `true` if the proof is valid, else `false` */ export function verifyMerkleProof(leaf, proof, root, options) { let hash = leaf; const sort = options?.sort !== false; for (const node of proof) { let pair; if (node.position === 'left') pair = [node.sibling, hash]; else pair = [hash, node.sibling]; if (sort && compareUint8(pair[0], pair[1]) > 0) pair = [pair[1], pair[0]]; hash = hashPair(pair[0], pair[1]); } return compareUint8(hash, root) === 0; }