Validating a Bitcoin Blocks Proof-of-Work in C#


Ever wonder how the Bitcoin Proof-of-Work is calculated? What data it uses, and how it’s manipulated to create a valid Proof-of-Work hash? 

Being someone who is interested in how Bitcoin operates under the hood, I’ve wanted to understand how this works for a while now. For example, I want to be able to open up a blockchain explorer, copy the data that I find there, and validate that the block I am seeing really has a valid PoW hash. Essentially, a Bitcoin miner that does not create and mine blocks, but instead accepts existing blocks as input for validation. 

After some researching and scraping together of code, I finally have my own functioning PoW validator. Today, I am going to show you how the Bitcoin PoW calculation is created, and how to make your own validator using C#.

Disclaimer: A basic understanding of PoW, and how SHA256 hashes work is recommended.


Getting Started

Im going to tackle this guide in 4 basic steps:

  1. Briefly explain how Bitcoin PoW operates.
  2. Explain how to get the data that is required for PoW hashing.
  3. Explain how to convert this data to a format that is ready for the hashing to be applied.
  4. Validate the PoW hash using our gathered block information and C# code.


Bitcoin PoW

Bitcoin PoW is calculated by performing a double SHA256 hash over specific parameters that are included in a Bitcoin block. These required PoW parameters are shown in the table below. 

I have provided a small description for each, as well as links to additional information. (See also the full Bitcoin Reference explanation of these parameters)

Version The block version. Indicates what validation rules to use.
Previous Block Hash The successful PoW hash from the last mined block.
Merkle Root Hash The hash of all transactions in the block. Learn more about how this is calculated here.
Timestamp An estimated time of when the miner started mining the block.
Bits The difficulty target that the PoW hash must meet to be valid. (read: the required number of 0s in the hash)
Nonce The random number that miners change in order to create a hash that meets the difficulty target.

Once we have the values for each of these parameters, they must be concatenated together and hashed. This produces a Bitcoin PoW formula that looks like this:

SHA256(SHA256(nVersion + HashPrevBlock + HashMerkleRoot + nTime + nBits + nNonce))

Now, this is a somewhat simplified formula, as these parameters must also be converted to different data types before they are concatenated together. Sadly, they cannot just be copied from a block explorer and pasted directly into a hash function.

I will cover the necessary conversion procedures for each parameter later on in this post. For now, lets move on with the process of validating a block.

Getting The Data

To begin solving the PoW for a block, we need to collect the pieces of data that were outlined in the previous section. This task is rather easy, as all of the data we need can be scraped from almost any blockchain explorer that you like. All we need to do is pick a block. We’ll pick the somewhat recently mined Block #503266.

Scraping this data from the blockchain explorer we get the following:

 Version  0x20000000
Previous Block Hash 00000000000000000033972c9263e9f1b593a4a555157ecc97735bf2fdea7664
Merkle Root Hash  ad873aebe594e6fa58c828b131d397c4d804355007499f8c9ad8e1a9e000b21a
 Timestamp  2018-01-09 01:40:31
 Bits 402690497
 Nonce  3801010522

That’s all the data we will need. The next part, converting this data for hashing, will be the most tricky. 


Converting The Data

As I mentioned above, before we can concatenate these parameters together for hashing, they must be properly converted to the correct data types. Because all parameters are going to undergo at least some formatting changes, this will be the most complicated portion of the guide. In the table below, I have outline each conversion that will occur on each parameter.

(If you have trouble understanding this table, I will be walking through the conversion process for each parameter shortly.)

  1. Convert to integer
  2. Convert to hex
  3. Swap every 2 characters 
  4. Reverse the hex string (little-endian conversion)
Previous Block Hash
  1. Swap every 2 characters 
  2. Reverse the string (little-endian conversion)
Merkle Root Hash
  1. Swap every 2 characters 
  2. Reverse the string (little-endian conversion)
  1. Convert Date to integer from Unix Epoch (covered later)
  2. Convert integer to hex
  3. Swap every 2 characters 
  4. Reverse the hex string (little-endian conversion)
  1. Convert integer to hex
  2. Swap every 2 characters 
  3. Reverse the hex string (little-endian conversion)
  1. Convert integer to hex
  2. Swap every 2 characters 
  3. Reverse the hex string (little-endian conversion)

Before we move on to the the actual conversion process for each parameters, I want to mention a few things.

You may have noticed that all of these values are going to undergo what’s listed as a little-endian conversion. For those that are not familiar with what little-endian is, you can find a detailed explanation here.

In basic terms, little-endian means that we will swap every two characters (every 1 byte) in the string (e.g. 1234 becomes 2143) and then we reverse that string (e.g. 2143 becomes 3412).

You may have also noticed that each parameter will be converted to hex, at some point. You can read more about hexadecimal here.

In the following sections, I will explain (to my best ability) the conversion process for each parameter in detail. Please refer to the table above if you get lost at any point. Keep in mind that I only want you to understand how the data is being put together. Later, in the code section, I will provide the necessary code to complete these formatting/conversion operations automatically.

1. Formatting the Version

Converting the Version field to the correct format is slightly complicated due to the variation of different versions that you will see across different blocks in the blockchain. Some examples that you may see on a block explorer are 1, 2, 3, 4, 0x20000000, and 0x30000000, to name a few. If you are unfamiliar with these version numbers, they can be used to signal support for different upgrades and forks in the Bitcoin protocol. Please see BIP9 for more information.

Due to the wide range of version choices, there are a multiple ways to process the conversion procedure. For simplicity in our code (later on), we are going to convert the value to an integer, then to hex, and then perform a little-endian conversion.

To begin this process, we convert the hex value 0x20000000 to an integer, producing the integer value 536870912. We then convert the integer value 536870912 to hex, which produces the value 20000000. A little-endian conversion of 20000000 produces the value 00000020, which is the value that will be used in our PoW calculation. This conversion process is outlined in the table below.

Version (starting data) 0x20000000
Version (convert to integer) 536870912
Version (integer converted to hex) 20000000
Version (hex swapped) 02000000
Version (hex reversed) 00000020

Note: Version 1, 2, 3, and 4 are already in integer format and may start with directly converting to hex.

2. Formatting the Hashes
Formatting the Previous Block Hash and Merkle Root Hash will be slightly easier than the Version parameter. All we will need to do is preform a little-endian conversion of the string.

First, we swap every 2 characters (1 bytes). Then, we reverse the entire string.

Previous Block Hash (starting data) 00000000000000000033972c9263e9f1b593a4a555157ecc97735bf2fdea7664
Previous Block Hash (swapped) 0000000000000000003379c229369e1f5b394a5a5551e7cc7937b52fdfae6746
Previous Block Hash (reversed) 6476eafdf25b7397cc7e1555a5a493b5f1e963922c9733000000000000000000

This process is duplicated for the Merkle Root hash:

Merkle Root Hash (starting data) ad873aebe594e6fa58c828b131d397c4d804355007499f8c9ad8e1a9e000b21a
Previous Block Hash (swapped) da78a3be5e496eaf858c821b133d794c8d4053057094f9c8a98d1e9a0e002ba1
Merkle Root Hash (reversed) 1ab200e0a9e1d89a8c9f4907503504d8c497d331b128c858fae694e5eb3a87a

And that’s it. The reversed data will be used when calculating the PoW hash.

3. Formatting the Timestamp

If you happened to notice the conversion steps for this parameter, you may be wondering what the Unix Epoch is. The Unix Epoch is a computing standard that tracks the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds. You can find out more information about this (as well as an online Unix Epoch converter) here. This standard is how we can consistently convert a timestamp to a valid integer number.

The timestamp conversion process is as follows:

Timestamp (starting data) 2018-01-09 01:40:31
Timestamp (converted to Epoch integer) 1515462031
Timestamp (integer converted to hex) 5a541d8f
Timestamp (hex swapped) a545d1f8
Timestamp (hex reversed) 8f1d545a

Timestamp reversed will be the data used in the PoW calculation.

4. Formatting the Bits

Formatting the bits is straight forward, and barrows from the same methods we have already used. First, we convert the value to hex, then perform a little-endian conversion.

Bits (starting data) 402690497
Bits (converted to hex) 180091c1
Bits (hex swapped) 8100191c
Bits (hex reversed) c1910018

Bits reversed will be the data used in the PoW calculation.

5. Formatting the Nonce

Formatting the Nonce is trivial, as it follows the exact same process as the bits. Convert the value to hex, then perform a little-endian conversion.

Nonce (starting data) 3801010522
Nonce (converted to hex) e28ed15a
Nonce (hex swapped) 2ee81da5
Nonce (hex reversed) 5ad18ee2

Nonce reversed will be the data used in the PoW calculation.


Conversion Conclusion

That’s it for converting the data! The final step will be to concatenate all of these values together, convert them to Bytes, and plug them into the hash function to fully validate a mined block.

To verify that we are all on the same page, I want to display the exact data that will be passed to our SHA256 hash.

Version 00000020
Previous Block Hash 6476eafdf25b7397cc7e1555a5a493b5f1e963922c9733000000000000000000
Merkle Root Hash 1ab200e0a9e1d89a8c9f4907503504d8c497d331b128c858fae694e5eb3a87ad
Timestamp 8f1d545a
Bits c1910018
Nonce 5ad18ee2


Coding the PoW Validation

I assure you that the hard part is done. We have the data we need, and we understand what format it needs to be in. All that’s left now is to put it into code. And we can do that in less than 100 lines of C#.

Now, instead of breaking it out into a bunch of sections, I’m simply going to paste the entire working code, and provide comments for each and every important line. These comments will act as the guide for this portion of the post.

Note: I am using the default Console App project in Visual Studio Community 2017 targeting .NET 4.6.1. This code is optimized for ease of understanding, not performance, and should not be used in any real world scenarios.



If you are following along, you should be able to run the application and see the following output:

And that’s all there is to it! I hope that this guide has been educational to everyone reading, and that you now have a better understanding of how the Bitcoin PoW and mining process takes place.

I pulled from many, many resources to create this guide. Thank you for stopping by, and please visit the following links for additional information and learning:

Information / Guides:

Bitcoin mining the hard way: the algorithms, protocols, and bytes – Similar guide in Python.

Block Wiki (PoW Hashing Algorithm) – Good breakdown of PoW in Python and PHP.

Bitcoin Developer Reference (Block Headers) – Overview of a Bitcoin block.

Minimal-Bitcoin-Miner – Fully functioning Bitcoin miner in C#.

Bitcoin Developer Reference – Great in depth look at Bitcoin under the hood.

Online data conversion tools:

Epoch & Unix Timestamp Conversion Tool

Little Endian Conversion Tool

Decimal to Hex converter