Skip to content

Instantly share code, notes, and snippets.

@ChristopherJohnston
Created December 21, 2017 01:07
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ChristopherJohnston/42b4a8af11c2d02ef2cc2c972fe6ee5d to your computer and use it in GitHub Desktop.
Save ChristopherJohnston/42b4a8af11c2d02ef2cc2c972fe6ee5d to your computer and use it in GitHub Desktop.
Example Blockchain hash calculations using Python
from hashlib import sha256
import time
import struct
import binascii
import datetime
VERSION = 1
def hexify(value, type):
return binascii.hexlify(struct.Struct(type).pack(value))
def get_block_header(hash_merkle_root, previous_hash, timestamp, bits, nonce):
'''
SEE: https://en.bitcoin.it/wiki/Block_hashing_algorithm
header comprises:
|================+============================================================+=========================================================+==============|
| Field | Purpose | Updated when... | Size (Bytes) |
|================|============================================================|=========================================================|==============|
| Version | Block version number | You upgrade the software and it specifies a new version | 4 |
| hashPrevBlock | 256-bit hash of the previous block header | A new block comes in | 32 |
| hashMerkleRoot | 256-bit hash based on all of the transactions in the block | A transaction is accepted | 32 |
| Time | Current timestamp as seconds since 1970-01-01T00:00 UTC | Every few seconds | 4 |
| Bits | Current target in compact format | The difficulty is adjusted | 4 |
| Nonce | 32-bit number (starts at 0) | A hash is tried (increments) | 4 |
|================+============================================================+=========================================================+==============|
'''
# Example header using data from https://blockchain.info/block/00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
# header_hex = ("01000000" +
# "81cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000" +
# "e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122b" +
# "c7f5d74d" +
# "f2b9441a" +
# "42a14695")
header_hex = '{version}{previous_hash}{hash_merkle_root}{timestamp}{bits}{nonce}'.format(
version=hexify(VERSION, '<L'),
previous_hash=binascii.hexlify(previous_hash.decode('hex')[::-1]),
hash_merkle_root=binascii.hexlify(hash_merkle_root.decode('hex')[::-1]),
timestamp=hexify(timestamp, '<L'),
bits=hexify(bits, "<L"),
nonce=hexify(nonce, '<L')
)
header_bin = header_hex.decode('hex')
return header_bin
def calculate_target(bits):
# http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html
# https://gist.github.com/shirriff/cd5c66da6ba21a96bb26#file-mine-py
# https://gist.github.com/shirriff/cd5c66da6ba21a96bb26#file-mine-py
# https://en.bitcoin.it/wiki/Difficulty
exp = bits >> 24
mant = bits & 0xffffff
target_hexstr = '%064x' % (mant * (1<<(8*(exp - 3))))
target_str = target_hexstr.decode('hex')
return target_str
def calc_hash(block):
hash = sha256(sha256(block).digest()).digest()
return hash[::-1]
def find_hash(block_number, data, difficulty_bits, previous_hash, initial_nonce=0, max_attempts=1000000000, timestamp=None):
print 'Find hash for block {0} with difficulty {1}'.format(block_number, difficulty_bits)
complete = False
nonce = initial_nonce
attempts = 0
h = None
while True:
timestamp = timestamp or int(time.time())
block = get_block_header(data, previous_hash, timestamp, difficulty_bits, nonce)
h = calc_hash(block)
target = calculate_target(difficulty_bits)
if h < target:
complete = True
break
nonce += 1
attempts += 1
if attempts > max_attempts:
break
return dict(
block_number=block_number,
timestamp=timestamp,
data=data,
previous_hash=previous_hash,
bits=difficulty_bits,
complete=complete,
attempts=attempts,
nonce=nonce if complete else None,
hash=h.encode('hex') if complete else None
)
def run_block(chain, block_number, data, difficulty_bits, initial_nonce=0, max_attempts=1000000000):
previous_hash = chain[block_number-1].get('hash') if block_number>0 else '0'*64
result = find_hash(block_number, data, difficulty_bits, previous_hash, initial_nonce, max_attempts)
print result
chain.append(result)
return chain
def verify(result, previous_hash):
nonce = result.get('nonce')
hash = result.get('hash')
block_number = result.get('block_number')
data = result.get('data')
timestamp = result.get('timestamp')
bits = result.get('bits')
block = get_block_header(data, previous_hash, timestamp, bits, nonce)
new_hash = calc_hash(block).encode('hex_codec')
return dict(hash=new_hash, valid=new_hash == hash)
def verify_chain(chain):
print 'verifying chain...'
results = {}
previous_hash = '0'*64
for block in chain:
block_number = block.get('block_number')
result = verify(block, previous_hash)
previous_hash = result['hash']
results[block_number] = result['valid']
return results
def calc_chain():
print 'Calculating example chain...'
data = sha256('hello').digest()[::-1].encode('hex')
difficulty_bits = 0x1f00ffff
max_attempts = 1000000000
# Run Chain once
chain = []
for block_number in range(4):
run_block(chain, block_number, data, difficulty_bits, 0, max_attempts)
block_number += 1
print verify_chain(chain)
print 'injecting different data at block 1....'
# Change some data and find new hash
new_result = find_hash(
chain[1]['block_number'],
sha256('world').digest()[::-1].encode('hex'),
difficulty_bits,
chain[0]['hash'],
0,
max_attempts,
chain[1]['timestamp']
)
chain[1] = new_result
print 'verifying chain again...'
print verify_chain(chain)
def calc_block_125552():
# from block 125552
# see https://blockchain.info/block/00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
# see https://blockexplorer.com/block/00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
print 'Calculating and verifying hash for block 125552...'
prev_block_hash = '00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81'
hash_merkle_root = '2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3'
bits = 0x1a44b9f2
timestamp = 1305998791
nonce = 2504433986
# a = struct.Struct('I')
# print datetime.datetime.fromtimestamp(s.unpack('c7f5d74d'.decode('hex'))[0])
# print s.unpack('f2b9441a'.decode('hex'))
# print s.unpack('42a14695'.decode('hex'))
# https://en.bitcoin.it/wiki/Difficulty
# https://bitcoin.stackexchange.com/questions/30467/what-are-the-equations-to-convert-between-bits-and-difficulty
# difficulty from bits
# bits = 1a44b9f2
# 0x1a = number of bytes in a target, 0x44b9f2 = target prefix
# Genesis block difficulty = 0x1d00ffff
# print 0x00ffff * 2**(8*(0x1d - 3)) / float(0x44b9f2 * 2**(8*(0x1a - 3)))
# Calc hash and check if it's less than the target
target = calculate_target(bits)
header = get_block_header(hash_merkle_root, prev_block_hash, timestamp, bits, nonce)
hash = calc_hash(header)
print 'Hash: {0}'.format(hash.encode('hex_codec'))
print 'Target: {0}'.format(target.encode('hex'))
print 'Valid Hash: {0}'.format(hash < target)
# find the hash through iteration
print find_hash(125552, hash_merkle_root, bits, prev_block_hash, initial_nonce=nonce-2, max_attempts=1000000000, timestamp=timestamp)
print
def main():
calc_block_125552()
calc_chain()
main()
@ChristopherJohnston
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment