ms

GitHub

Verifying Bitcoin Proof-of-Work (PoW)

29 Nov 2025

I’ve been running a full Bitcoin node for some time now. At some point, after getting bored looking at the incoming transactions, I started wondering what is actually happening when my node receives new blocks. I knew the high-level idea ”a full node validates PoW”, but I wanted to understand how does the node verify PoW for a block header so I could verify it myself.

The code is here: https://gist.github.com/msolomatin/6d60b525fab2ad7012e5d76800b60574

What verifying PoW for a block header means in practice

Every Bitcoin block header is 80 bytes long. If you hash these 80 bytes twice with SHA-256, you get a 32-byte number. For the block to be valid, this number must be smaller than or equal to a network-defined target value. In other words, sha256(sha256(header)) ≤ target .

The target comes from the nBits field inside the block header. Bitcoin docs define nBits field as:

An encoded version of the target threshold this block’s header hash must be less than or equal to.

Here’s the coolest part which took me ages to digest - despite nBits is a 4-byte (32-bit) field, it’s a compressed form of a 256-bit difficulty value.

So verifying PoW for a single block boils down to two steps:

If the hash is too large, the block is invalid. If it’s below the target, the PoW checks out.

Calculating target is where things get interesting

Let’s get a hex-encoded data of a block header:

bitcoin-cli getblockheader 000000000000000000019549da0db5548e865c1f064b2a5336182aa790019858 false
00800a2038a1a28d2d0c41014e022e0fef3f6631ce794df4ea5c00000000000000000000af016ef8a32ce91447d0823894ad4b97bc0fb42b6d8fe98a574548cc69e9bee351332869a0e201171d4999b5

The nBits field sits at byte offset 72 in the 80-byte header and is 4 bytes long. In the hex-encoded header those 4 bytes appear as a0e20117, which is stored in little-endian. If we flip the byte order to big-endian - the way we normally read integers - we get 1701d936.

Convert nBits into the target

nBits field value 1701d936 has two parts. In Bitcoin terminology 0x17 is an exponent, 0x01e2a0 is a mantissa. There’s a formula for target calculation in Bitcoin docs:

converting-bits-into-a-target-threshold.png

Which in simple terms means:

To calculate the target, shift mantissa to the left (exponent - 3) number of bytes.

nBits: 0x1701e2a0

                          mantissa (01e2a0)
                          ------
Target: 00000000000000000001e2a00000000000000000000000000000000000000000
                          <---------------------------------------------
                          exponent (0x17 = 23 bytes)
                          shift mantissa 20 number of bytes

When you double-hash the header, you’ll get a number below this target — which is why the block is valid.

What I Found Interesting

But most importantly: anyone can verify Bitcoin’s work independently with a few dozen lines of code.