And a port to C#, for anyone who may want/need it.
This isn't exactly the most efficient way to do it but will serve the purpose. I've also done a couple of things to make the returned values slightly more convenient for consumption purposes.
A .NET pushpool will need this, which is why I bothered to take the time...
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
public class Block
{
private static DateTime UTC_ORIGIN = new DateTime(1970, 1, 1, 0, 0, 0, 0);
private static HashAlgorithm SHA256provider = Environment.OSVersion.Version.Major >= 6 ? new SHA256CryptoServiceProvider() : SHA256.Create();
public string Solution { get; private set; }
public long Version { get; private set; }
public long Header { get; private set; }
public string PreviousBlockHash { get; private set; }
public string MerkleRoot { get; private set; }
public DateTime TimeStamp { get; private set; }
public long Bits { get; private set; }
public long Nonce { get; private set; }
private string computedBlockHash = string.Empty;
public string BlockHash
{
get
{
// Lazy-load this when required since this is a CPU-intensive operation
if (string.IsNullOrEmpty(this.computedBlockHash))
{
this.computedBlockHash = ComputeBlockHash(this.GenerateHashableString());
}
return this.computedBlockHash;
}
}
public Block(string solution)
{
this.Solution = solution;
this.Construct();
}
private void Construct()
{
string raw = this.Solution.Substring(0, 160);
Version = Convert.ToInt64(raw.Substring(0, 8), 16); // @version ||= @raw[0, 8].hex
PreviousBlockHash = SwitchHexStringEndianness(raw.Substring(8, 64)); // @previous_block ||= switch_endianness(@raw[8, 64])
MerkleRoot = SwitchHexStringEndianness(raw.Substring(72, 64)); // @merkle_root ||= switch_endianness(@raw[72, 64])
TimeStamp = FromUnixTimestamp(Convert.ToInt64(raw.Substring(136, 8), 16)); // @timestamp ||= @raw[136, 8].hex
Bits = Convert.ToInt64(raw.Substring(144, 8), 16); // @bits ||= @raw[144, 8].hex
Nonce = Convert.ToInt64(raw.Substring(152, 8), 16); // @nonce ||= @raw[152, 8].hex
}
public override string ToString()
{
StringBuilder retBldr = new StringBuilder("Block:" + Environment.NewLine);
retBldr.AppendFormat("Solution={0}{1}", this.Solution, Environment.NewLine);
retBldr.AppendFormat("Version={0}{1}", this.Version, Environment.NewLine);
retBldr.AppendFormat("PreviousBlockHash={0}{1}", this.PreviousBlockHash, Environment.NewLine);
retBldr.AppendFormat("MerkleRoot={0}{1}", this.MerkleRoot, Environment.NewLine);
retBldr.AppendFormat("TimeStamp={0}{1}", this.TimeStamp, Environment.NewLine);
retBldr.AppendFormat("Bits={0}{1}", this.Bits, Environment.NewLine);
retBldr.AppendFormat("Nonce={0}{1}", this.Nonce, Environment.NewLine);
retBldr.AppendLine();
retBldr.AppendFormat("BlockHash={0}{1}", this.BlockHash, Environment.NewLine);
return retBldr.ToString();
}
private string GenerateHashableString()
{
return ReverseBytesInBinaryString(string.Format("{0:X8}", this.Version))
+ ReverseBytesInBinaryString(this.PreviousBlockHash)
+ ReverseBytesInBinaryString(this.MerkleRoot)
+ ReverseBytesInBinaryString(string.Format("{0:X8}", ConvertToUnixTimestamp(this.TimeStamp)))
+ ReverseBytesInBinaryString(string.Format("{0:X8}", this.Bits))
+ ReverseBytesInBinaryString(string.Format("{0:X8}", this.Nonce));
}
private static string SwitchHexStringEndianness(string str)
{
string result = "";
while (str.Length > 0)
{
result += str.Substring(str.Length - 8);
str = str.Substring(0, str.Length - 8);
}
return result;
}
private static string ComputeBlockHash(string hashableString)
{
byte[] bytes = ConvertHexStringToBinary(hashableString);
string hex = BytesToHexString(SHA256provider.ComputeHash(SHA256provider.ComputeHash(bytes)));
return ReverseBytesInBinaryString(hex);
}
private static string BytesToHexString(byte[] a)
{
StringBuilder sb = new StringBuilder(a.Length * 2 + 2);
foreach (byte b in a)
{
sb.AppendFormat("{0:X2}", b);
}
return sb.ToString().ToLower();
}
private static string ReverseBytesInBinaryString(string str)
{
return ConvertBinaryToHexString(ConvertHexStringToBinary(str));
}
private static string ConvertBinaryToHexString(byte[] bytes)
{
// Reverse the endianness of the byte array
Array.Reverse(bytes);
return BytesToHexString(bytes);
}
private static byte[] ConvertHexStringToBinary(string str)
{
List items = new List();
for (int i = 0; i < str.Length / 2; i++)
{
items.Add(Convert.ToByte(str.Substring(i * 2, 2), 16));
}
return items.ToArray();
}
private static DateTime FromUnixTimestamp(long timestamp)
{
return UTC_ORIGIN.AddSeconds((double)timestamp);
}
private static long ConvertToUnixTimestamp(DateTime date)
{
TimeSpan diff = date - UTC_ORIGIN;
return (long)diff.TotalSeconds; // Reminder: will truncate fractional seconds
}
}
Using is easy:
Block b = new Block("solution string");
Console.WriteLine(b.ToString());