@variablesoftware/ts-merkle
Version:
🌳🔗🛡️ A TypeScript library for creating and verifying Merkle trees.
132 lines (131 loc) • 4.2 kB
JavaScript
// 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;
}