Hashing in Merkle Trees

February 16, 2026

A hash function compresses the input into a fixed-length string. It can be used for compression itself, but might also be used to remove structure from a user’s input for security purposes (e.g. when signing a message).

A typical blockchain uses a hash function in the following common cases:

Here, we focus on the use of a hash function in Merkle trees.

Because hash functions compress data, collisions are inevitable, but they are hard to find. For current standards, not a single collision is known, and finding two points that collide - even by sheer luck - would likely render the hash function unsuitable for futher use.

Depending on the application, we require different security properties from a hash function:

*Due to birthday paradox a hash function that outputs t bits is at most t/2-bits collision resistant.

Note that collision resistance is the strongest property of all. Collision resistance implies second preimage resistance implies preimage resistance (for sufficiently long inputs which we will consider here).

And, occasionally, additional property is required (especially when hashing secrets):

Although for completeness, it should be noted that some exotic properties are sometimes considered for keyed hash functions, e.g. target collision resistance (TCR), enhanced target collision resistance (eTCR), etc.

For Merkle trees, we require a hash-function with at least 128-bits of collision resistance. By the birthday bound, this implies that the internal hash-function should produce at least 256 bits of output.

History of NIST standards for hash functions: The Secure Hash Algorithm (SHA) was developed by NSA and published by NIST in 1993 as FIPS-180. This first version is now refered to as SHA-0. SHA-0 outputs 160-bit digests, hence was only 80-bits collision resistant at best. In 1995, NSA added an extra instruction to the SHA-0’s compression function, NIST updated the standard accordingly without giving any rationale behind the change. The resulting function was called SHA-1. Ten years later it was found that this extra instruction was crucial for collision resistance. SHA-1 became the de-facto standard for collision resistant hashing. As SHA-1 started seeing more and more attacks, in 2001 SHA-2 developed by NSA was added to the standard, now becoming FIPS 180-2, it is three to four times slower than SHA-1. In 2011 NIST recommended to move from SHA-1 to SHA-2 as attacks discovered in 2005 in SHA-1 were becoming more practical. In February 2017 a collision was found as two distinct pdf documents, so for crypto-applications SHA-1 is mostly abandoned, although it is still believed to be a one-way function (i.e. to satisfy the preimage resistance property). Unlike SHA-1 and SHA-2, which were designed by the NSA and then released as public-use patents, SHA-3 was selected through a public competition organized by NIST. The competition began in 2017, and the winning design was standardized in 2012 as FIPS-202.

Current NIST standards for hashing (see NIST-FIPS-202):

Security Strength of SHA-1, SHA-2 and SHA-3

SHA-2

SHA-2 standardized in 2001 and specified in FIPS 180-4, is often informally refered to simply as “SHA”. It has modes that give 224, 256, 384 or 512-bits output.

SHA-2 follows the Merkle-Damgard paradigm with Davies-Meyer-type compression function. For a block-cipher E, the compression function iteratively processes fixed-size message blocks together with a chaining value:

Merkle Damgard Construction

The underlying block cipher E used for SHA-2 is NIST-designed SHACAL-2 cipher running the round function on a 64B block for SHA2-256 (or on a 128B block for SHA2-512) with different pre-specified round keys: 64 rounds for SHA2-256 and 80 rounds for SHA2-512. Attack on SHA2-512/256 (2016) found collision for 43 rounds (out of 80). Attack on SHA2-256 (2011) found collision for 46 rounds (out of 64). No practical attack are known on full SHA2 other than the trivial birthday-bound.

Two modes give 256-bits output: SHA-256 and SHA-512/256, the former is more frequently used. SHA2-512/256 is SHA2-512 with a different IV vector truncated to 256 bits. It is sometimes used in place of SHA2-256 as it is faster on 64-bits architectures and also resistant against length-extension attacks.

SHA2 Native Instruction: SHA2-256 has a native instruction in many modern processors making it about 10x faster! Even SHA2-512 is harware-supported on modern ARM processors.

Note that AES can’t easily be used out-of-the-box for E, since AES has 128-size blocks thus giving only 64-bits security for collision resistance. AES is also too slow for short-messages due to key-switching overhead.

Block size and padding. SHA2-256 has 64B-size blocks. Padding for SHA2 is 10*L, where L is a 64 bits long length (in bits) of the original message, so SHA2-256 the padding always adds at least 65 bits to the message. When hashing a 64B-input, the conventional padding will overflow the input making it two blocks instead of one, which for example is a 2x overhead for internal Merkle binary tree nodes. SHA2-512 has 128B-size blocks. The padding for SHA2-512 is 10*L, where L is a 128 bits long length (in bits) of the original message. So this padding adds at least 129 bits to the message.

Bitcoin is using SHA2-256 for the proof-of-work challenges. All of Bitcoin miners are collectively computing 2^95 hashes per year. Bitcoin also constructs a Merkle tree for a block with transactions as leaves, using double SHA2-256, the root of the tree is put into a block’s header.

SHA-3

SHA-3 (FIPS 202) was standardized in 2015. While there is nothing wrong with SHA-2 (as of today), SHA-3 got standardised to diversify the crypto-toolkit as new attacks were being discovered for SHA-1 which shares similar designs with SHA-2. So, in the event one of the functions get broken, we have another one to substitute with (though it has its own controversies).

SHA3 Sponge Construction
From “A Graduate Course in Applied Cryptography” version 0.6 by Dan Boneh and Victor Shoup

SHA-3 is a sponge construction. Where the function f is a public premutation called Keccak that maps 1600-bits to 1600-bits. For SHA3-256: c = 512 bits, for SHA3-512: c = 1024 bits. For SHAKE128: c = 256 bits, for SHAKE256: c = 512 bits. The output is r first bits of the last execution of f. The c last bits are not part of the output, giving length-extension resistance in contrast to SHA-2. f includes 24 rounds of iterations: an attack on SHA-3 (2019) found a collision on 5 rounds (out of 24); no attacks are known on full SHA3 other than the trivial birthday bound. SHA3 can be accelerated a bit with AVX vector instructions, and ARM-processors have accelerated hardware instructions for Keccak now.

Two modes give 256-bits output and have 128-bits of collision resistance: SHA3-256 and SHAKE128 (with 256-bits output).

Padding and block-size. Padding for SHA3 is 01 || 10*1, for SHAKE is 1111 || 10*1. SHA3 blocks are larger than SHA2 (for SHA3-256 blocks are 136B).

Ethereum is using Keccak-256 (which is not SHA3, though quite the same): Ethereum launched just before FIPS 202 was published (August 2015) but after the Keccak was announced as a winner, so Ethereum’s hash function has some minor differences from SHA3. It’s used in different Merkle trees, including for accounts’ states. It had been used for proof-of-work mechanism, which since retired in favor of proof-of-stake.

Hashing in Merkle Trees

Hashing in a tree-fasion is done to either parallelize hashing, or to make it easier to do small changes to the large data being hashed, or for Merkle-tree accumulators which serves as cryptographic commitments with efficiently computable proofs of inclusion.

Under some plausible assumptions, it was shown that to get (n*256)-to-256 collision-resistant function from 512-to-256 collision-resistant function, n-1 calls are required, so Merkle trees and Merkle-Damgard construction (e.g. SHA-2) are in some sense optimal.

Sequentian vs. Parallel Hashing

The trees in accumulators can get rather big, for example:

Performance

Let’s measure performance of NIST hash functions with 256-bits outputs. I run benchmarks on Rust crates on Apply M3 Pro (2023), and for long inputs I’ve got:

Rust hashing benchmarks

In the “-ASM” modes, the code is compiled for “native” cpu, and uses available hardware instructions, which means native instructions for SHA2 and SHA512/256, and AVX acceleration. With no “-ASM” modes, the code is compiled for “generic” cpu, default features are disabled (except 'force-soft' on SHA2 to enforce software implementation). Rust version 1.91.1, and crate versions: SHA2 0.10.9, SHA3 0.10.8.

This is obviously just a playground-type benchmark. There are more robust and detailed benchmarks available, most notably SUPERCOP.

Let’s take a closer look at the left side of the graph above and see how those function perform on small inputs. The graphs jump in steps with the block-length as expected:

Rust hashing benchmarks

Finally, for performance in binary Merkle trees, let’s look at a specific point of these graphs for hashing 64 bytes input. In this case, the function is 2-to-1 compressing, it takes 512-bits input and gives 256-bits output. The difference in performance is quite remarkable. Some of the functions that tend to be very fast on long inputs perform poorly on inputs that are these small:

328 ns SHAKE128
270 ns SHAKE128-ASM
248 ns SHA2
165 ns SHA3
162 ns SHA512-256
134 ns SHA3-ASM
128 ns SHA2 no padding
70 ns SHA512-256-ASM
45 ns SHA2-ASM
25 ns SHA2-ASM no padding

Droping the padding/domain-separation would require careful in-protocol handling (to maintain collision-resistance of the tree construction) at the benefit of better performance.

Some highly-secure applications might want to have a security boundary between storage and execution; and check merkle proofs on every read from the database (for example, an HSM using external memory). Performance of the hash-function will then become even more important.

Other notable hash functions

DISCLAIMER: Opinions expressed here are my own and may change without notice. The content on this blog is for informational purposes only and should not be considered technical, legal, or investment advice. While I strive for accuracy, I cannot guarantee that all information is correct or complete, and I am not responsible for any errors or how the content is used. Readers should consult their own advisers regarding any technical, legal, or other professional matters.