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