Example of finding a nonce to solve Proof of Work

The following example will illustrate how to find a nonce by brute force, with the help of the SHA-256 algorithm, in order to find a hash value that will satisfy the target hash. The target hash value is determined by setting the difficulty bits in the Proof of Work algorithm. We will modify the blockchain linker that we created earlier on to include the Proof of Work algorithm while creating a new block. Firstly, let's modify a few functions of the blockchain example to include the consensus algorithm.

The Block class used to create a new block to be added to blockchain is modified to take two extra members, called difficulty_bits and nonce. We'll also include difficulty_bits in the header of any Proof of Work-based blockchain application:

from Crypto.Hash import SHA256 
from datetime import datetime 
 
class Block(object): 
    """A class representing the block for the blockchain""" 
 
    def __init__(self, index, previous_hash, timestamp, data, 
difficulty_bits, nonce, hash): self.index = index self.previous_hash = previous_hash self.timestamp = timestamp self.data = data self.difficulty_bits = difficulty_bits self.nonce = nonce self.hash = hash

self.difficulty_bits is also included in the Blockchain class to use as a parameter while a miner is performing the Proof of Work algorithm:

class Blockchain(object): 
    """A class representing list of blocks""" 
 
    def __init__(self): 
 
        self._chain = [self.get_genesis_block()] 
        self.timestamp = int(datetime.now().timestamp())
self.difficulty_bits = 0

The create_block function will add both nonce and difficulty_bits, which are both set by the user while mining a new block. This information is included in the block to verify the block later once it is broadcast to every node in the network:

def create_block(self, block_data): 
    """creates a new block with the given block data""" 
 
    previous_block = self.get_latest_block() 
    next_index = previous_block.index + 1 
    next_timestamp = self.timestamp 
    next_hash, next_nonce = self.calculate_hash(next_index, 
previous_block.hash, next_timestamp, block_data) return Block(next_index, previous_block.hash, next_timestamp,
block_data, self.difficulty_bits, next_nonce, next_hash)

The calculate_hash method is then modified to compute a header and call the function to perform Proof of Work by computing the nonce:

def calculate_hash(self, index, previous_hash, timestamp, data): 
    """calculates SHA256 hash value by solving hash puzzle""" 
 
    header = str(index) + previous_hash + str(timestamp) + data + 
str(self.difficulty_bits) hash_value, nonce = self.proof_of_work(header) return hash_value, nonce

The proof_of_work method performs a search on each nonce by incrementing its value in order to find a hash value that is less than the target value. The target value is computed by using the difficulty_bits value provided.

Each time a hash value is calculated using the SHA256 hash function, the resulting hexadecimal digest is converted to a decimal value and compared with the target decimal value. If the computed hash value is less than the target value, it signifies that the hash value starts with a value that is greater than or equal to the difficulty_bits, so the nonce and the hash value is returned:

def proof_of_work(self, header): 
 
    target = 2 ** (256 - difficulty_bits) 
 
    for nonce in range(max_nonce): 
        hash_result = SHA256.new(data=(str(header) + 
str(nonce)).encode()).hexdigest() if int(hash_result, 16) < target: print("Success with nonce %d" % nonce) print("Hash is %s" % hash_result) return (hash_result, nonce) print("Failed after %d (max_nonce) tries" % nonce) return nonce

The following Python main method will create a new Blockchain object class that will create a new chain with a genesis block. The for loop will create a new block, each time increasing difficulty_bits by 1. The proof_of_work function will be invoked each time a block is created:

max_nonce = 2 ** 32  # 4 billion 
 
if __name__ == '__main__': 
 
    new_chain = Blockchain() 
 
    for difficulty_bits in range(32): 
        difficulty = 2 ** difficulty_bits 
        new_chain.difficulty_bits = difficulty_bits 
        print("Difficulty: %ld (%d bits)" % (difficulty, 
difficulty_bits)) print("Starting search...") start_time = datetime.now() new_block_data = 'test block with transactions' new_chain.add_block(data=new_block_data) end_time = datetime.now() elapsed_time = (end_time - start_time).total_seconds() print("Elapsed Time: %.4f seconds" % elapsed_time) if elapsed_time > 0: hash_power = float(int(new_chain.chain[-
1].get("nonce")) / elapsed_time) print("Hashing Power: %ld hashes per second" %
hash_power)

We'll find the output of the proof of work example as follows. It prints out the hashing power of the system used, the nonce, and the time elapsed during the creation of the block:

Difficulty: 1 (0 bits) 
Starting search... 
Success with nonce 0 
Hash is 
365190b63a9ae8443e9dfb7463bcac6c207c29cdd0e8a5f251285d4d5ddbacb3 Elapsed Time: 0.0029 seconds Hashing Power: 0 hashes per second Difficulty: 2 (1 bits) Starting search... Success with nonce 0 Hash is
67aad7ed255c1f7f3b6427cb75c60b6a9520c1ed19747d4f62b701691958f3b7 Elapsed Time: 0.0001 seconds Hashing Power: 0 hashes per second [...] Difficulty: 64 (6 bits) Starting search... Success with nonce 4 Hash is
0061db8a0100345e7a1675d39f8c5dae34c89712365d9761e30546c0dbb17e6d Elapsed Time: 0.0002 seconds Hashing Power: 17316 hashes per second Difficulty: 128 (7 bits) Starting search... Success with nonce 22 Hash is
0129f5f8dfae6063b09da9b6655848e4797c0ac22e1dba97dca6d9e6bfdbf6cb Elapsed Time: 0.0009 seconds Hashing Power: 23732 hashes per second [...] Difficulty: 33554432 (25 bits) Starting search... Success with nonce 3819559 Hash is
0000001085152816d24bf7f32625295a0617a719b72f4f868e06003329975a9d Elapsed Time: 122.2431 seconds Hashing Power: 31245 hashes per second Difficulty: 67108864 (26 bits) Starting search... Success with nonce 12980169 Hash is
0000003435ba522e2c2d52fc7ad31b144103a99694299621a2a0573fb6f6be9c Elapsed Time: 410.8903 seconds Hashing Power: 31590 hashes per second Difficulty: 134217728 (27 bits) Starting search...

It is quite evident from this example output that the time taken to compute the solution is directly proportional to the number of difficulty bits used. To be precise, for every bit increase in the difficulty level, the probability of finding the nonce decreases by half because the target space decreases by half. Although occasionally luck might help us solve some puzzles, probability theory holds for the majority of cases with the proof of work consensus algorithm.