Merkle trees: inclusion and consistency proofs (RFC 6962)
leafHash / nodeHash / emptyRootHash build the tree hashes with the two domain-separation prefixes fixed by the spec: a leaf is SHA-256(0x00 || entry), an interior node is SHA-256(0x01 || left || right), the empty tree is SHA-256(""). Those 0x00 / 0x01 prefixes are the second-preimage defense: without them a leaf whose bytes equal a valid interior node's preimage could be smuggled in as present.
verifyInclusion folds an audit path back to a root and constant-time- compares it to a trusted checkpoint root; verifyConsistency reconstructs both the old and the new root from a consistency proof (the append-only guarantee lives in the old-root leg). Both are fail-closed: a malformed coordinate, an out-of-range index, an inverted window, a wrong hash length, or a proof whose node count does not match the tree geometry throws a typed merkle/* error; the one boolean-false result is the final root comparison ("root matched" vs "did not"). A false from verifyInclusion means "not proven present against this root", never "validly absent": an inclusion proof cannot express absence. Tree coordinates are uint64, carried as BigInt so a large index is never Number-narrowed. This is not a DER format. Like pki.ct it is a companion module reached explicitly, never routed by the detect-and-parse orchestrator.
pki.merkle.leafHash
pki.merkle.leafHash(entry) -> Buffer
The Merkle leaf hash MTH({d}) = SHA-256(0x00 || entry). The 0x00 prefix is the leaf-domain second-preimage separation and is applied unconditionally. Throws merkle/bad-input if entry is not a Buffer / Uint8Array.
Example
pki.merkle.leafHash(Buffer.from("leaf data")); // -> <Buffer 32-byte leaf hash>
References
pki.merkle.nodeHash
pki.merkle.nodeHash(left, right) -> Buffer
The Merkle interior-node hash SHA-256(0x01 || left || right). Both operands must be 32-byte hashes; the 0x01 prefix is applied unconditionally. Throws merkle/bad-input on a non-buffer operand, merkle/bad-hash-length on an operand that is not exactly 32 bytes.
Example
var l = pki.merkle.leafHash(Buffer.from([0]));
var r = pki.merkle.leafHash(Buffer.from([1]));
pki.merkle.nodeHash(l, r); // -> <Buffer 32-byte node hash>
References
pki.merkle.emptyRootHash
pki.merkle.emptyRootHash() -> Buffer
The Merkle tree head of the empty tree, MTH({}) = SHA-256("") (e3b0c442...b855). A fresh Buffer each call.
Example
pki.merkle.emptyRootHash(); // -> <Buffer e3 b0 c4 42 ...>
References
pki.merkle.verifyInclusion
pki.merkle.verifyInclusion(opts) -> boolean
Verify an RFC 6962 / RFC 9162 audit (inclusion) proof: fold leafHash up the audit path and constant-time-compare the reconstructed root to rootHash. Returns true iff the proof binds the leaf to the root; a well-formed proof that does not match returns false, meaning "not proven present against this root" and never "validly absent". A malformed input throws a typed merkle/* error: a coordinate that is not a non-negative integer (or a Number >= 2^53), treeSize 0, leafIndex >= treeSize, a non-32-byte hash, or a proof whose node count does not match the tree geometry.
Options
leafIndex: number | bigint, // 0-based leaf position (uint64; pass BigInt above 2^53)
treeSize: number | bigint, // total leaf count of the tree the root commits to
leafHash: Buffer, // 32-byte leaf hash (e.g. from pki.merkle.leafHash)
proof: Buffer[], // the audit path, each node a 32-byte hash
rootHash: Buffer, // 32-byte trusted checkpoint root
Example
var lh = pki.merkle.leafHash(Buffer.from([0]));
pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
References
pki.merkle.verifyConsistency
pki.merkle.verifyConsistency(opts) -> boolean
Verify an RFC 6962 / RFC 9162 consistency proof between an older tree of oldSize leaves (root oldRoot) and a newer tree of newSize leaves (root newRoot). Reconstructs both roots from the proof and constant-time-compares each; returns true iff both match. The append-only guarantee lives in the old-root leg: a proof that yields a valid newRoot but the wrong oldRoot is a rewritten history and returns false. Equal non-zero sizes require an empty proof and oldRoot == newRoot.
An oldSize of 0 with a non-empty newer tree is refused as merkle/no-consistency-claim. RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize: the empty tree is a prefix of every tree by definition, so there is no proof to check and nothing at all binds newRoot. Two empty trees are still answered: that is the degenerate identity case, and both roots must be emptyRootHash().
A malformed input throws a typed merkle/* error: oldSize > newSize, a non-empty proof where the geometry requires none (or empty where it requires one), a non-32-byte hash, or a wrong node count.
Options
oldSize: number | bigint, // leaf count of the older tree (uint64)
newSize: number | bigint, // leaf count of the newer tree (>= oldSize)
oldRoot: Buffer, // 32-byte root of the older tree
newRoot: Buffer, // 32-byte root of the newer tree
proof: Buffer[], // the consistency proof, each node a 32-byte hash
Example
var r = pki.merkle.leafHash(Buffer.from([0]));
pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
References
pki.merkle.root
pki.merkle.root(leafHashes) -> Buffer
The RFC 6962 Merkle Tree Hash MTH(D[n]) over a whole tree, returned as a 32-byte root ready to pass to verifyInclusion as rootHash. Takes leaf HASHES, the same form verifyInclusion takes as leafHash; map entries through pki.merkle.leafHash first. An empty array is the empty tree and returns emptyRootHash().
Throws merkle/bad-input if leafHashes is not an array or a leaf is not a Buffer / Uint8Array, merkle/bad-hash-length if a leaf is not exactly 32 bytes, and merkle/too-many-leaves above C.LIMITS.MERKLE_MAX_LEAVES.
Example
var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
pki.merkle.root(leaves); // -> <Buffer 32-byte tree head>
References
pki.merkle.inclusionProof
pki.merkle.inclusionProof(opts) -> Buffer[]
Produce the RFC 6962 sec. 2.1.1 audit path PATH(leafIndex, D[n]) for one leaf of a tree held in memory. The returned array is the proof that verifyInclusion folds against root(leafHashes).
The tree size is the length of leafHashes and is not a separate option, so a size and a leaf array cannot disagree. Throws merkle/empty-tree on an empty tree, merkle/index-out-of-range for a leafIndex at or above the leaf count, and the leafHashes errors root throws.
Options
leafHashes: Buffer[], // the whole tree, each leaf a 32-byte hash
leafIndex: number | bigint, // 0-based position of the leaf to prove
Example
var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
pki.merkle.inclusionProof({ leafHashes: leaves, leafIndex: 0 }); // -> [<Buffer ...>]
References
pki.merkle.consistencyProof
pki.merkle.consistencyProof(opts) -> Buffer[]
Produce the RFC 6962 sec. 2.1.2 consistency proof PROOF(oldSize, D[n]) between an older tree of oldSize leaves and the tree held in leafHashes. The returned array is the proof that verifyConsistency checks against root(leafHashes.slice(0, oldSize)) and root(leafHashes).
The newer size is the length of leafHashes and is not a separate option. Equal sizes produce the empty proof, which is what the verifier requires there. Throws merkle/old-size-exceeds-new when oldSize is past the leaf count and merkle/no-consistency-claim for an oldSize of 0 against a non-empty tree, matching verifyConsistency: RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, so there is no proof to produce.
Options
leafHashes: Buffer[], // the newer tree, each leaf a 32-byte hash
oldSize: number | bigint, // leaf count of the older tree
Example
var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
pki.merkle.consistencyProof({ leafHashes: leaves, oldSize: 1 }); // -> [<Buffer ...>]