Nemanja Grubor
22 Jun 2021
•
10 min read
In this article, we will talk about building a blockchain in Python. Blockchain is the fundamental building block behind Bitcoin, as well as some other cryptocurrencies, which are digital currencies.
Let's say you would like to launch your own currency. Let us call this TestCoin. We will write a blockchain to record all transactions that deal with TestCoin. There may be other service providers who would join our network and start accepting TestCoin as the currency for giving out their services.
The blockchain project development consists of three major components:
The client is the one who will buy goods from other vendors. The client may become a vendor and will accept money from others against the goods he supplies. We assume that the client can be both, a supplier and a recipient of TestCoins.
The miner is the one who picks up the transactions from a transaction pool and assembles them in a block. All the money that the miner collects as a fee will be for him to keep.
The Blockchain is a data structure that chains all mined blocks in chronological order.
A client is somebody who holds TestCoins and transacts those for goods (or services) from other vendors on the network including his own. We will define a Client class for this purpose. To create a globally unique identifier for the client, we use PKI (Public Key Infrastructure)
The client should be able to send money from his wallet to another known person. Similarly, the client should be able to accept money from a third party. For spending money, the client would create a transaction specifying the sender's name and the amount to be paid. For receiving money, the client would provide his identity to the third party - a sender of the money. During a transaction, we will compute the actual balance to ensure that the client has sufficient balance to make the payment.
To develop the Client class, we will need to import some Python libraries:
import hashlib
import random
import string
import json
import binascii
import numpy as np
import pandas as pd
import pylab as pl
import logging
import datetime
import collections
import Crypto
import Crypto.Random
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
```## Client Class
The **Client** class generates the private and public keys by using the built-in Python RSA algorithm. During the object initialization, we create private and public keys and store their values in the instance variable:
```python
self._private_key = RSA.generate(1024, random)
self._public_key = self._private_key.publickey()
Note that you should never lose your private key. The generated public key will be used as the client's identity. For this, we define a property called identity that returns the HEX representation of the public key:
@property
def identity(self):
return
binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii')
The identity is unique to each client and can be made publicly available. Anybody would be able to send virtual currency to you using this identity and it would be added to your wallet.
Client class:
class Client:
def __init__(self):
random = Crypto.Random.new().read
self._private_key = RSA.generate(1024, random)
self._public_key = self._private_key.publickey()
self._signer = PKCS1_v1_5.new(self._private_key)
@property
def identity(self):
return
binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii')
We will write code that illustrates how to use the Client class:
John = Client()
print(John.identity)
The above code creates an instance of Client class and assigns it to the variable John. We print the public key of John by calling its identity method.
We will create a Transaction class so that the client will be able to send money. When you want to receive money, some other sender will create a transaction and specify your public address in it.
Transaction class:
def __init__(self, sender, recipient, value):
self.sender = sender
self.recipient = recipient
self.value = value
self.time = datetime.datetime.now()
The init method takes three parameters - the sender's public key, the recipient's public key, and the amount to be sent. These are stored in the instance variables for use by other methods. We create one more variable for storing the time of transaction.
Next, we write a utility method called to_dict that combines all the four above-mentioned instance variables in a dictionary object. This is just to put the entire transaction information accessible through a single variable.
The first block in the blockchain is a Genesis block. This block contains the first transaction initiated by the creator of the blockchain. The identity of this person may be kept a secret like in the case of Bitcoins. So, when this first transaction is created, the creator may just send his identity as Genesis. While creating the dictionary, we check if the sender is Genesis and if so, we simply assign some string value to the identity variable. If not, we assign the sender's identity to the identity variable:
if self.sender == "Genesis":
identity = "Genesis"
else:
identity = self.sender.identity
We construct the dictionary as follows:
return collections.OrderedDict({'sender': identity,
'recipient': self.recipient,
'value': self.value,
'time': self.time})
to_dict method:
def to_dict(self):
if self.sender == "Genesis":
identity = "Genesis"
else:
identity = self.sender.identity
return collections.OrderedDict({'sender': identity,
'recipient': self.recipient,
'value': self.value,
'time': self.time})
We will sign this dictionary object using the private key of the sender. We use the built-in PKI with SHA algorithm. The generated signature is decoded to get the ASCII representation for printing and storing it in our blockchain.
The sign_transaction method:
def sign_transaction(self):
private_key = self.sender._private_key
signer = PKCS1_v1_5.new(private_key)
h = SHA.new(str(self.to_dict()).encode('utf8'))
return binascii.hexlify(signer.sign(h)).decode('ascii')
We will create two users, John and Joe. John will send 5 TestCoins to Joe. First, we create clients called John and Joe.
John = Client()
Joe = Client()
As John is sending payment to Joe, he will need the public key of Joe which is obtained by using the identity property of the client.
We create the transaction instance:
t = Transaction(
John,
Joe.identity,
5.0
)
The sign_transaction method retrieves the sender's private key from the first parameter. After the transaction object is created, you will sign it by calling its sign_transaction method. This method returns the generated signature in a printable format.
We generate and print the signature:
signature = t.sign_transaction()
print(signature)
Our basic infrastructure of creating a client and a transaction is ready. We will now have multiple clients doing multiple transactions as in a real-life situation.
The transactions made by various clients are queued in the system. The miners pick up the transactions from this queue and add them to the block. They will then mine the block and the winning miner would have the privilege of adding the block to the blockchain and earn money.
The display_transaction function accepts a single parameter of transaction type. The dictionary object within the received transaction is copied to a temporary variable called dict and using the dictionary keys, the various variables are printed:
def display_transaction(transaction):
dict = transaction.to_dict()
print("sender: " + dict['sender'])
print('---------')
print("recipient: " + dict['recipient'])
print('---------')
print("value: " + str(dict['value']))
print('---------')
print("time: " + str(dict['time']))
print('---------')
Now, we define a transaction queue for storing transaction objects.
To create a queue, we declare a global list variable called transactions as follows:
transactions = []
We will append each newly created transaction to this queue.
We will start creating transactions. First, we will create four clients who will send money to each other:
John = Client()
Joe = Client()
Ann = Client()
Jane = Client()
We assume that each of these clients holds some TestCoins in their wallets for transacting. The identity of reach of these clients would be specified by using the identity property of these objects.
We initiate the first transaction:
t1 = Transaction(
John,
Joe.identity,
15.0
)
In this transaction, John sends 15 TestCoins to Joe. For a transaction to be successful, we will have to ensure that John has sufficient money in his wallet for this payment. We will need a genesis transaction to start TestCoin circulation in the system.
We will sign this transaction using John's private key and add it to the transaction queue:
t1.sign_transaction()
transactions.append(t1)
We will create several more transactions. When somebody spends money, it is not necessary that he has to check for sufficient balances in his wallet. The miner would be validating each transaction for the balance that the sender has while initiating the transaction. In case of insufficient balance, the miner will mark this transaction as invalid and would not add it to the block.
The following code creates and adds two more transactions to the queue:
t2 = Transaction(
John,
Ann.identity,
6.0
)
t2.sign_transaction()
transactions.append(t2)
t3 = Transaction(
Joe,
Jane.identity,
2.0
)
t3.sign_transaction()
transactions.append(t3)
When you run the above code, you will have those transactions in the queue for the miners to create their blocks.
You may periodically like to review the contents of the transaction queue. For this purpose, you can use the display_transaction function that we developed earlier. To dump all transactions in the queue, iterate the transactions list and for each referenced transaction, call the display_transaction function:
for transaction in transactions:
display_transaction(transaction)
print('----------------------------')
The transactions are separated by a line for distinction.
A block consists of a varying number of transactions. As the block needs to store the transactions list, we will declare an instance variable called verified_transactions:
self.verified_transactions = []
Each block also holds the hash value of the previous block, so that the chain of blocks becomes immutable.
To store the previous hash, we declare an instance variable:
self.previous_block_hash = ""
We declare one more variable called Nonce for storing the nonce (number only used once) created by the miner during the mining process:
self.Nonce = ""
Block class:
class Block:
def __init__(self):
self.verified_transactions = []
self.previous_block_hash = ""
self.Nonce = ""
As each block needs the value of the previous block's hash, we declare a global variable called last_block_hash:
last_block_hash = ""
We assume that the originator of TestCoins initially gives out 500 TestCoins to a known client John. For this, he first creates a John instance:
John = Client()
Then, we create a genesis transaction and send 500 TestCoins to John's public address:
t0 = Transaction(
"Genesis",
John.identity,
500.0
)
We create an instance of Block class - block0.
block0 = Block()
We initialize the previous_block_hash and Nonce instance variables to None, as this is the first transaction to be stored in our blockchain.
block0.previous_block_hash = None
Nonce = None
We will add t0 transaction to the verified_transactions list maintained within the block:
block0.verified_transactions.append(t0)
The block is completely initialized and is ready to be added to our blockchain. We will create the blockchain for this. Before we add the block to the blockchain, we will hash the block and store its value in the global variable last_block_hash. This value will be used by the next miner in his block.
Hashing the block and storing the digest value:
digest = hash(block0)
last_block_hash = digest
A blockchain contains a list of blocks chained to each other. To store the entire list, we will create a list variable - TestCoins:
TestCoins = []
We will also write a method - dump_blockchain, for dumping the contents of the entire blockchain. We first print the length of the blockchain so that we know how many blocks are currently present in the blockchain:
def dump_blockchain(self):
print("Number of blocks: " + str(len(self)))
To iterate through the chain, we set up a for loop:
for x in range(len(TestCoins)):
block_temp = TestCoins[x]
Each referenced block is copied to a temporary variable - block_temp.
We print the block number as a heading for each block:
print("block #" + str(x))
Within each block, we have stored a list of transactions in a variable - verified_transactions. We iterate this list in a for loop and for each retrieved item, we call the display_transaction function to display the transaction details.
for transaction in block_temp.verified_transactions:
display_transaction(transaction)
The full function definition:
def dump_blockchain(self):
print("Number of blocks: " + str(len(self)))
for x in range(len(TestCoins)):
block_temp = TestCoins[x]
print("block #" + str(x))
for transaction in block_temp.verified_transactions:
display_transaction(transaction)
print('--------------------------------')
print('=====================================')
Adding a block to the blockchain involves appending the created block to the TestCoins list:
TestCoins.append(block0)
Now, you will dump the contents of the blockchain by calling the global function - dump_blockchain:
dump_blockchain(TestCoins)
For enabling mining, we need to develop a mining function. The mining functionality needs to generate a digest on a given message string.
We will write a function - sha256 for creating a digest on a given message:
def sha256(message):
return hashlib.sha256(message.encode('ascii')).hexdigest()
We develop the mine function that implements our mining strategy. Our strategy would be to generate a hash on the given message that is prefixed with a given number of 1's. The given number of 1's is specified as a parameter to mine function specified as the difficulty level.
def mine(message, difficulty=1):
assert difficulty >= 1
prefix = '1' * difficulty
We will check if this prefix exists in the generated digest of the message. To digest the message, we use the following:
for i in range(1000):
digest = sha256(str(hash(message)) + str(i))
We check if the digest value has above-set prefix:
if digest.startswith(prefix):
If the condition is satisfied, we will terminate the for loop and return the digest value to the caller.
The full mine code:
def mine(message, difficulty=1):
assert difficulty >= 1
prefix = '1' * difficulty
for i in range(1000):
digest = sha256(str(hash(message)) + str(i))
if digest.startswith(prefix):
print("after " + str(i) + " iterations found nonce: " + digest)
return digest
Each miner will pick up the transactions from a previously created transaction pool. To track the number of messages already mined, we create a global variable:
last_transaction_index = 0
We will now have our first miner adding a block to the blockchain.
To add a new block, we first create an instance of the Block class:
block = Block()
We pick up the top three transactions from the queue:
for i in range(3):
temp_transaction = transactions[last_transaction_index]
After the transaction is validated, we add it to the verified_transactions list in the block instance:
block.verified_transactions.append(temp_transaction)
We increment the last transaction index so that the next miner will pick up subsequent transactions in the queue:
last_transaction_index += 1
We add exactly three transactions to the block. Once done, we will initialize the rest of the instance variables of the Block class. We first add the hash of the last block:
block.previous_block_hash = last_block_hash
Next, we mine the block with a difficulty level of 2:
block.Nonce = mine(block, 2)
We now hash the entire block and create a digest on it:
digest = hash(block)
Finally, we add the created block to the blockchain and re-initialize the global variable last_block_hash for use in the next block.
The full code for adding the block:
block = Block()
for i in range(3):
temp_transaction = transactions[last_transaction_index]
block.verified_transactions.append(temp_transaction)
last_transaction_index += 1
block.previous_block_hash = last_block_hash
block.Nonce = mine(block, 2)
digest = hash(block)
TestCoins.append(block)
last_block_hash = digest
We will add two more blocks to our blockchain:
# Miner 2 adds a block
block = Block()
for i in range(3):
temp_transaction = transactions[last_transaction_index]
block.verified_transactions.append(temp_transaction)
last_transaction_index += 1
block.previous_block_hash = last_block_hash
block.Nonce = mine(block, 2)
digest = hash(block)
TestCoins.append(block)
last_block_hash = digest
# Miner 3 adds a block
block = Block()
for i in range(3):
temp_transaction = transactions[last_transaction_index]
block.verified_transactions.append(temp_transaction)
last_transaction_index += 1
block.previous_block_hash = last_block_hash
block.Nonce = mine(block, 2)
digest = hash(block)
TestCoins.append(block)
last_block_hash = digest
You can verify the contents of the entire blockchain:
dump_blockchain(TestCoins)
This was a rough explanation of how to implement a blockchain project in Python.
Nemanja Grubor
See other articles by Nemanja
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!