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.
Im going to tackle this guide in 4 basic steps:
- Briefly explain how Bitcoin PoW operates.
- Explain how to get the data that is required for PoW hashing.
- Explain how to convert this data to a format that is ready for the hashing to be applied.
- Validate the PoW hash using our gathered block information and C# code.
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:
|Previous Block Hash||00000000000000000033972c9263e9f1b593a4a555157ecc97735bf2fdea7664|
|Merkle Root Hash||ad873aebe594e6fa58c828b131d397c4d804355007499f8c9ad8e1a9e000b21a|
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.)
|Previous Block Hash||
|Merkle Root Hash||
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.
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.
|Previous Block Hash||6476eafdf25b7397cc7e1555a5a493b5f1e963922c9733000000000000000000|
|Merkle Root Hash||1ab200e0a9e1d89a8c9f4907503504d8c497d331b128c858fae694e5eb3a87ad|
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.
static void Main(string args)
//Stage our data from a block explorer. Copied directly from Block #503266.
string nVersion = "0x20000000";
string HashPrevBlock = "00000000000000000033972c9263e9f1b593a4a555157ecc97735bf2fdea7664";
string HashMerkleRoot = "ad873aebe594e6fa58c828b131d397c4d804355007499f8c9ad8e1a9e000b21a";
string nTime = "2018-01-09 01:40:31";
string nBits = "402690497";
string nNonce = "3801010522";
//Format the data.
nVersion = Reverse(Swap(ToHex(Convert.ToUInt32(nVersion, 16)))); //Read in the version as an integer. Convert the integer back to hex. Swap every two characters in the string. Then, Reverse the string.
HashPrevBlock = Reverse(Swap(HashPrevBlock)); //Swap every two characters in the HashPrevBlock string. Then, Reverse the string. See the also the Swap() and Reverse() methods.
HashMerkleRoot = Reverse(Swap(HashMerkleRoot)); //Swap every two characters in the HashMerkleRoot string. Then, Reverse the string. See the also the Swap() and Reverse() methods.
nTime = Reverse(Swap(ToHex(DateToEpoch(nTime)))); //Convert the timestamp to a valid integer using Unix Epoch. Convert the integer value to hex. Swap every two characters in the value. Reverse the string.
nBits = Reverse(Swap(ToHex(UInt32.Parse(nBits)))); //Convert the bits to an integer (required because we are inputting it as a string). Convert the integer to hex. Swap every two characters in the value. Reverse the string.
nNonce = Reverse(Swap(ToHex(UInt32.Parse(nNonce))));//Convert the nonce to an integer (required because we are inputting it as a string). Convert the integer to hex. Swap every two characters in the value. Reverse the string.
//Send our pre-hashed data to the console for viewing. This should match the data that we converted earlier in the guide.
Console.WriteLine("nVersion: " + nVersion);
Console.WriteLine("HashPrevBlock: " + HashPrevBlock);
Console.WriteLine("HashMerkleRoot: " + HashMerkleRoot);
Console.WriteLine("nTime: " + nTime);
Console.WriteLine("nBits: " + nBits);
Console.WriteLine("nNonce: " + nNonce);
//Calculate the PoW
string blockInfo = nVersion + HashPrevBlock + HashMerkleRoot + nTime + nBits + nNonce; //Concatenate our formatted data into the exact Bitcoin PoW order.
byte blockDataToHash = ToBytes(blockInfo); //Convert our formatted data into bytes, the final step before hashing.
byte blockPoWHash = DoubleSha256(blockDataToHash); //Perform a double SHA256 on the byte array (the PoW).
string successfulHash = ToString(blockPoWHash); //Convert our PoW hash back to a readable string.
//Display our results
Console.WriteLine("Block PoW Hash: " + Reverse(Swap(successfulHash))); //Reverse the PoW hash so that the 0s are at the start, and display to the user.
public static string Reverse(string input)
return new string(input.ToCharArray().Reverse().ToArray()); //Convert the input string to a char array -> reverse the values in this array -> convert and return back the string.
public static string Swap(string input)
string swappedString = "";
for (int i = 0; i < input.Length; i += 2)
//Loop through the entire string, selecting and reversing every 2 characters. The conditional operator (? :) is to handle cases where the string is of uneven length. In which case, we just a select the last and single character.
swappedString += new string((input.Substring(i, input.Length >= i+2 ? 2 : 1)).ToArray().Reverse().ToArray());
public static byte ToBytes(string input)
byte bytes = new byte[(input.Length + 1) / 2]; //Create a Byte array that is half the length of the string input. The length+1 portion is to assure that the array size always rounds up, and has enough available space (note: 1 byte can hold 2 characters).
for (int i = 0, j = 0; i < input.Length; j++, i += 2)
bytes[j] = byte.Parse(input.Substring(i, input.Length >= i+2 ? 2 : 1), System.Globalization.NumberStyles.HexNumber);
//Loop through the entire string, selecting and converting every 2 characters to bytes. The conditional operator (? :) is to handle cases where the string is of uneven length. In which case, we just a select the last and single character and convert it to bytes.
public static string ToString(byte input)
string result = "";
foreach (byte b in input)
result += b.ToString("x2"); //Convert every byte (2 characters) in the array back to a string value.
public static string ToHex(UInt32 input)
return input.ToString("X8").ToLower(); //Convert the integer value to hex, making sure that it is all lowercase. X8 also ensures that the result is padded to at least 8 characters.
public static byte DoubleSha256(byte input)
SHA256Managed _hasher = new SHA256Managed(); //The built in .NET library for performing SHA256 hashing.
byte crypto = _hasher.ComputeHash( _hasher.ComputeHash(input) ); //Run two rounds of SHA256 hashing over our byte array.
return crypto; //Return the SHA256 hash.
public static UInt32 DateToEpoch(string input)
return (UInt32)(Convert.ToDateTime(input) - new DateTime(1970, 1, 1)).TotalSeconds; //Calculate the number of seconds that have elapsed since the given date and the Unix Epoch. Return the integer value.
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: