3

I want to calculate/verify the hash for block #722,460.

Here is the JSON:

  "hash": "00000000000000000002b73f69e81b8b5e98dff0f2b7632fcb83c050c3b099a1",
  "confirmations": 9,
  "height": 722460,
  "version": 536879108,
  "versionHex": "20002004",
  "merkleroot": "e1e0573e6098d8128ee859e7540f56b01fe0a33e56694df6d2fab0f96c4954b3",
  "time": 1644403033,
  "mediantime": 1644398983,
  "nonce": 1693537958,
  "bits": "170a8bb4",
  "difficulty": 26690525287405.5,
  "chainwork": "000000000000000000000000000000000000000028664e27abbdcf002a673476",
  "nTx": 3371,
  "previousblockhash": "00000000000000000009d54a110cc122960d31567d3ee84a1f18a98f50591046",
  "nextblockhash": "0000000000000000000a6bf48e80b7b873998e9b8e7c4ced6ec85d559bce72df",
  "strippedsize": 821777,
  "size": 1534464,
  "weight": 3999795,

To calculate hash I need theese values (already have converted them):

version: '04200020'
previousblockhash: '461059508fa9181f4ae83e7d56310d9622c10c114ad509000000000000000000'
merkleroot: 'b354496cf9b0fad2f64d69563ea3e01fb0560f54e759e88e12d898603e57e0e1'
time: '3330404416'
bits: 'b48b0a17'
nonce: 'a656f164' 

Now I am trying to calculate the hash:

import hashlib
from binascii import unhexlify, hexlify
header_hex = ("04200020" +
 "461059508fa9181f4ae83e7d56310d9622c10c114ad509000000000000000000" +
 "b354496cf9b0fad2f64d69563ea3e01fb0560f54e759e88e12d898603e57e0e1" +
 "3330404416" +
 "b48b0a17" +
 "a656f164")
header_bin = unhexlify(header_hex)
hash = hashlib.sha256(hashlib.sha256(header_bin).digest()).digest()
hexlify(hash[::-1]).decode("utf-8")

And all I get is: b3e5b3485f11963cff6659fb48cfecc6e1cda271324d8b25b1af023205bb891b

Instaed of: 00000000000000000002b73f69e81b8b5e98dff0f2b7632fcb83c050c3b099a1

What am I doing wrong?

Thank you in advance

4
  • 4
    Your timestamp doesn't look like a 4-byte hexadecimal number. Feb 9, 2022 at 14:38
  • It is hex 4- byte. I have tried different ways. Also have tried 87890362. But thank you for your comment!
    – Yuri
    Feb 10, 2022 at 11:26
  • Can't understand a thing! Just got block header in hex: 04200020461059508fa9181f4ae83e7d56310d9622c10c114ad509000000000000000000b354496cf9b0fad2f64d69563ea3e01fb0560f54e759e88e12d898603e57e0e159990362b48b0a17a656f164 The timestamp in hex is 59990362, as you can find from the string. But if we convert it in decimal it takes 1503200098 and not 1644403033 if we get in the header in JSON.
    – Yuri
    Feb 10, 2022 at 11:41
  • 1
    59990362 is little-endian, you first have to convert it to big-endian (62039959) before you convert it to decimal. Feb 10, 2022 at 15:42

2 Answers 2

1

After a lot of research, I've come up with the following Javascript function that calculates the valid hash of any block provided you input the 6 required parameters that are officially used to generate the hash of a Bitcoin block header. I think it may help people understand how a block header is correctly hashed.

Parameters of this example are set with Bitcoin Block #722,460.

/**
 * @dev This function takes the version, timestamp, bits or nonce and returns
 * its representation as a hexadecimal string in little-endian byte order
 * 
 */
function toLittleEndianHex(value) {
  // Create a new 4-byte little-endian DataView with an ArrayBuffer
  const littleEndian = new DataView(new ArrayBuffer(4));
  // Set the value of the DataView to the version, timestamp, bits or nonce in little-endian byte order
  littleEndian.setUint32(0, value, true);
  // Convert the DataView into a Uint8Array
  const byteArray = new Uint8Array(littleEndian.buffer);
  // Convert each byte into a 2-character hexadecimal string, and push them into an array
  const hexArray = [];
  for (let i = byteArray.length - 1; i >= 0; i--) {
    hexArray.push(byteArray[i].toString(16).padStart(2, "0"));
  }
  // Reverse the order of the hexadecimal strings in the array and join them into a single string
  return hexArray.reverse().join("");
}

/**
 * @dev This function takes a hexadecimal string and returns its 
 * representation as a Uint8Array
 * 
 */
function hexStringToUint8Array(hexString) {
  // Convert the hexadecimal string into an array of 2-character substrings, representing each byte
  const byteArray = hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16));
  // Create a new Uint8Array from the array of bytes
  return new Uint8Array(byteArray);
}

/**
 * @dev This function takes a hexadecimal string and returns its reverse
 * 
 */
function reverseHexString(hexString) {
  // Convert the hexadecimal string into an array of 2-character substrings, representing each byte
  // Reverse the order of the substrings and join them into a single string
  return hexString.match(/.{1,2}/g).reverse().join("");
}

/**
 * @dev This function takes an object representing block data, calculates 
 * its hash value, and logs it to the console
 * 
 */
async function calculateBlockHash(blockData) {
  // Destructure the block data object to extract its properties
  const { version, previousblockhash, merkleroot, time, bits, nonce } = blockData;
  // Convert each property to its hexadecimal representation in little-endian byte order
  const versionHex = toLittleEndianHex(version);
  console.log("versionHex:", versionHex);
  const previousblockhashHex = reverseHexString(previousblockhash);
  console.log("previousblockhashHex:", previousblockhashHex);
  const merklerootHex = reverseHexString(merkleroot);
  console.log("versionHex:", merklerootHex);
  const timeHex = toLittleEndianHex(time);
  console.log("timeHex:", timeHex);
  const bitsHex = toLittleEndianHex(bits);
  console.log("bitsHex:", bitsHex);
  const nonceHex = toLittleEndianHex(nonce);
  console.log("nonceHex:", nonceHex);
  // Concatenate the hexadecimal representations into a single string representing the block header
  const headerHex = `${versionHex}${previousblockhashHex}${merklerootHex}${timeHex}${bitsHex}${nonceHex}`;
  console.log("headerHex:", headerHex);
  // Convert the header hexadecimal string into a Uint8Array
  const headerBin = hexStringToUint8Array(headerHex);
  // Use the Web Crypto API to calculate the double-SHA256 hash of the block header
  const hashDigest1 = await crypto.subtle.digest("SHA-256", headerBin);
  const hashDigest2 = await crypto.subtle.digest("SHA-256", hashDigest1)
  // Convert the hash digest into a Uint8Array
  const hashBin = new Uint8Array(hashDigest2);
  // Convert the hash Uint8Array into a hexadecimal string with reversed byte order
  const hashHex = Array.from(hashBin, b => b.toString(16).padStart(2, "0")).join("");
  const hashResult = hashHex.match(/.{1,2}/g).reverse().join("");
  // Log the hash result value to the console
  console.log("hashResult:", hashResult);
}

/**
 * @dev Data from Block #722,460
 * Verify at https://bitcoinexplorer.org/block-height/722460#JSON
 * 
 */
const blockData = {
  version: 536879108,
  previousblockhash: "00000000000000000009d54a110cc122960d31567d3ee84a1f18a98f50591046",
  merkleroot: "e1e0573e6098d8128ee859e7540f56b01fe0a33e56694df6d2fab0f96c4954b3",
  time: 1644403033,
  bits: 0x170a8bb4,
  nonce: 1693537958
};

// Call the calculateBlockHash function with the blockData object as its argument
await calculateBlockHash(blockData);

Console Output:

versionHex: 04200020
previousblockhashHex: 461059508fa9181f4ae83e7d56310d9622c10c114ad509000000000000000000
versionHex: b354496cf9b0fad2f64d69563ea3e01fb0560f54e759e88e12d898603e57e0e1
timeHex: 59990362
bitsHex: b48b0a17
nonceHex: a656f164
headerHex: 04200020461059508fa9181f4ae83e7d56310d9622c10c114ad509000000000000000000b354496cf9b0fad2f64d69563ea3e01fb0560f54e759e88e12d898603e57e0e159990362b48b0a17a656f164
hashResult: 00000000000000000002b73f69e81b8b5e98dff0f2b7632fcb83c050c3b099a1
0

We need to concatenate these fields in correct format and order:

"versionHex": "20002004", "previousblockhash": "00000000000000000009d54a110cc122960d31567d3ee84a1f18a98f50591046", "merkleroot": "e1e0573e6098d8128ee859e7540f56b01fe0a33e56694df6d2fab0f96c4954b3", "time": 1644403033, "bits": "170a8bb4", "nonce": 1693537958.

Problem here is that not all fields are given with the same encoding and are not in correct byte order. The decimal numbers have to be converted to little endian hex, and other hex values need to have the bytes reversed.

Here's a little shell script that does the job:

#!/bin/bash

# Input fields
versionHex=20002004
previousblockhash=00000000000000000009d54a110cc122960d31567d3ee84a1f18a98f50591046
merkleroot=e1e0573e6098d8128ee859e7540f56b01fe0a33e56694df6d2fab0f96c4954b3
time=1644403033
bits=170a8bb4
nonce=1693537958

# Function to convert a decimal number to hex big endian.
NumToHex () { echo "obase=16;$1" | bc | tr -d '\n'; }

# Function to change endianness, e.g. 01a2 becomes a201.
# Input must have even number of characters.
ReverseBytesHex () { echo -n "$1" | tac -rs '..'; }

# Function to calculate block hash from serialized block header.
BlockHash () { echo $1 -n | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -b 1-64 | tr -d '\n' | tac -rs '..'; }

# Generate serialized block from input fields.
serializedBlock=$(ReverseBytesHex $versionHex)
serializedBlock=$serializedBlock$(ReverseBytesHex $previousblockhash)
serializedBlock=$serializedBlock$(ReverseBytesHex $merkleroot)
timeHex=$(NumToHex $time)
serializedBlock=$serializedBlock$(ReverseBytesHex $timeHex)
serializedBlock=$serializedBlock$(ReverseBytesHex $bits)
nonceHex=$(NumToHex $nonce)
serializedBlock=$serializedBlock$(ReverseBytesHex $nonceHex)

# Generate and print block's hash.
echo $(BlockHash $serializedBlock)

Output:

00000000000000000002b73f69e81b8b5e98dff0f2b7632fcb83c050c3b099a1

For reference, please see the raw block header format below.

Block Header Format

Field Length Format Description
version 4 bytes unsigned integer(LE) The block format version.
previous block hash 32 bytes block hash(LE) The hash of the block immediately preceding this block in the blockchain.
merkle root 32 bytes merkle root(LE) The merkle tree root of the transactions in the block.
timestamp 4 bytes unix timestamp(LE) The epoch timestamp of the block in seconds.
target 4 bytes compressed target(LE) The target that the block hash must be below to be valid. This value is determined by the timestamps of previously mined blocks.
nonce 4 bytes bytes(LE) A random value that is repeatedly changed during block mining in order to achieve the block hash requirements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.