Proof of work (PoW) - add security to your block chain

By: Varghese Chacko 1 year, 1 month ago

Proof of Work is the method by which the legitimacy of a transaction is verified. Let us try adding proof of work to the previous block chain we created at this post.

Two major flaws of our implementation are, 1. Anyone can create huge number of blocks to the chain using a regular computer 2. With super computational power, anyone can tamper with a block and regenerate the hash for entire chain and make the chain valid. We can overcome this by using a mechanism called proof of work (PoW). This is an algorithm or process that required heavy computational power. With this, you prove that you put lot of computing power in creating a block. This process is also called mining.

For example, Bit Coin's block chain required hash of a block should start with a certain number of zeros. Because we can not influence the output of a hash function, we have to try a lot of combinations of the hash and match for out criteria. The number of leading zeros in the block's hash is called 'difficulty'. In bit coin's case, the aim is to create one block every ten minutes. As time goes, computational power gets better and requires less time to mine a block. This is compensated by increasing the difficulty. To implement this, we are going to add a new function in the block namedmine_block() As you know, hash wont change as long as the data is same. We can not change index, time or data, because it all constitute the transaction. So we add an attribute named nonce with has nothing to do with the block, but used to generate valid hash. It can be any random number. So we initialize nonce with 0 and generate hash, Then we increment it with 1 till we get a valid hash. Themine_block() could be  

    def mine_block(self, difficulty):
        """Function to mine a block."""
        while not self.hash.startswith('0' * dificulty):
            self.hash = self.generate_hash()
            self.nonce += 1

        print("Mining success: {hash}".format(hash=self.hash))

Now add the nonce to the the hash generating function. New hash generationg code would be

    def generate_hash(self):
        """Calculate hash of the block."""
        m = hashlib.sha256(
            "{index}{previous_hash}{timestamp}{data}{nonce}".format(
                index=self.index,
                previous_hash=self.previous_hash,
                timestamp=self.timestamp,
                data=json.dumps(self.data),
                nonce=self.nonce,
            ).encode('utf-8')
        )
        return m.hexdigest()

Now let us change our chain to add the difficulty and add block function to use mine_block() function. Let us say, our difficulty is 3 for now.

    def __init__(self):
        """Cunstructor for blockchain."""
        self.difficulty = 3
        self.chain = [self.create_genesis_block()]

 Change the add_block function to use the mining function, to generate hash.

    def add_block(self, new_block):
        """Add new block to chain."""
        new_block.previous_hash = self.get_last_block().hash
        # new_block.hash = new_block.generate_hash()
        new_block.mine_block(self.difficulty)
        self.chain.append(new_block)

Let us run this and now Now, my output is 

Mining block 1...
Mining success: 0009e369fd7e2b08b631c8dae642a002e11c05b72ae9dc21c06b173be5fe8614
Mining block 2...
Mining success: 0006d6fa9ce99ebf5991d84c564381d19eac27fc4119a31b3ac5ccb40b90aed1

[
  {
    "index": 0,
    "previous_hash": null,
    "data": "Genesis block for ATEMON Coin Chain",
    "hash": "0003a7b90e7713c1239d64947e231e8dcdfa362ae1879a46f2e12b81c2901e80",
    "timestamp": "01/01/2018"
  },
  {
    "index": 1,
    "previous_hash": "0003a7b90e7713c1239d64947e231e8dcdfa362ae1879a46f2e12b81c2901e80",
    "data": {
      "amount": 10
    },
    "hash": "0009e369fd7e2b08b631c8dae642a002e11c05b72ae9dc21c06b173be5fe8614",
    "timestamp": "02/01/2018"
  },
  {
    "index": 2,
    "previous_hash": "0009e369fd7e2b08b631c8dae642a002e11c05b72ae9dc21c06b173be5fe8614",
    "data": {
      "amount": 25
    },
    "hash": "0006d6fa9ce99ebf5991d84c564381d19eac27fc4119a31b3ac5ccb40b90aed1",
    "timestamp": "04/01/2018"
  }
]

See, our hash has three leading zeros. The difficulty three is calculated pretty fast, less than a second. So let us increase difficulty to 5 and see. For me, it took more than 15 seconds to mine one block, which is far better than the initial difficulty of fraction of a second. The algorithm for proof of work should be made more complex and complicated to have more security.  The full code of this example is:

"""Block chain example."""
# Block Chain

import hashlib
import json


class AtemonBlock(object):
    """The block for ATEMON Coin."""

    def __init__(self, index, timestamp, data, previous_hash=None):
        """Constructor for Block."""
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previous_hash = previous_hash
        self.nonce = 0
        self.hash = ''

    def generate_hash(self):
        """Calculate hash of the block."""
        m = hashlib.sha256(
            "{index}{previous_hash}{timestamp}{data}{nonce}".format(
                index=self.index,
                previous_hash=self.previous_hash,
                timestamp=self.timestamp,
                data=json.dumps(self.data),
                nonce=self.nonce,
            ).encode('utf-8')
        )
        return m.hexdigest()

    def __repr__(self):
        """Stringify for pretty printing."""
        return json.dumps({
            "index": self.index,
            "timestamp": self.timestamp,
            "data": self.data,
            "previous_hash": self.previous_hash,
            "hash": self.hash,
        })

    def mine_block(self, difficulty):
        """Function to mine a block."""
        while not self.hash.startswith('0' * difficulty):
            self.hash = self.generate_hash()
            self.nonce += 1

        print("Mining success: {hash}".format(hash=self.hash))


class AtemonBlockChain(object):
    """The block chain for ATEMON coin."""

    def __init__(self):
        """Cunstructor for blockchain."""
        self.difficulty = 5
        self.chain = [self.create_genesis_block()]

    def create_genesis_block(self):
        """Create first block of the chain."""
        genesis_block = AtemonBlock(0, "01/01/2018", "Genesis block for ATEMON Coin Chain", None)
        genesis_block.mine_block(self.difficulty)
        return genesis_block

    def get_last_block(self):
        """Get last block of the chain."""
        return self.chain[-1]

    def add_block(self, new_block):
        """Add new block to chain."""
        new_block.previous_hash = self.get_last_block().hash
        # new_block.hash = new_block.generate_hash()
        new_block.mine_block(self.difficulty)
        self.chain.append(new_block)

    def is_valid(self):
        """Check the chain is valid."""
        for index, current_block in enumerate(self.chain[1:]):  # Skip the Genesis block
            previous_block = self.chain[index]  # because we skipped one block and the index is 1 less than actual index
            if current_block.hash != current_block.generate_hash():
                return False
            if current_block.previous_hash != previous_block.hash:
                return False
        return True


if __name__ == '__main__':
    import pprint  # Here because it is used only for boiler plate
    atemon_coin = AtemonBlockChain()
    print("Mining block 1...")
    atemon_coin.add_block(AtemonBlock(1, "02/01/2018", {"amount": 10}))
    print("Mining block 2...")
    atemon_coin.add_block(AtemonBlock(2, "04/01/2018", {"amount": 25}))

    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(atemon_coin.chain)

 

I hope now you understand what a proof of work (PoW) is, How its implemented and why it is implemented.