import hashlib as hs
import timeIn this post I explain how to program a blockchain, specifically a cryptocurrency, from scratch in Python. I recommend that you take a look at my other post that explains how a blockchain works.
simple blockchain
We need to install the haslib library to implement the hash function.
Terminal
pip install haslibWe import the haslib and time libraries:
Transactions
classDiagram
class Transaction
Transaction : +sender
Transaction : +receiver
Transaction : +value
We’ll start by creating the Transaction class. Objects of this type collect the properties of the transactions, these are the sender, receiver and value of the transaction:
class Transaction:
def __init__(self, sender, receiver, value):
self.sender = sender
self.receiver = receiver
self.value = valueBlock
classDiagram
class Block
Block : +timestamp
Block : +List~Transaction~ transactions
Block : +prevHash
Block : +hash
Block : +nonce
Block : +calculateHash()
Block : +mineBlock(difficulty)
We define a Block class that represents each block on the blockchain. The blocks have as attributes: the set of transactions (transactions), the time stamp (timestamp) that indicates when the block was created, the hash of the previous block (prevHash), the hash of the block (hash) and the free variable (nonce). The calculateHash() function calculates the hash of the block using the SHA256 algorithm and taking the block’s attributes as input. The mineBlock(difficulty) function executes the proof of work in a loop that increments the nonce variable until it finds the corresponding hash that starts with the number of zeroes specified by the mining difficulty.
class Block:
def __init__(self, timestamp, transactions):
self.timestamp = timestamp
self.transactions = transactions
self.prevHash = None
self.hash = None
self.nonce = 0
def calculateHash(self):
return hs.sha256((''.join([self.timestamp, ''.join(str(x) for x in self.transactions), self.prevHash, str(self.nonce)])).encode()).hexdigest()
def mineBlock(self, difficulty):
while True:
self.hash = self.calculateHash()
if self.hash[0:difficulty] == "".join(["0" for x in range(difficulty)]):
break
self.nonce += 1
print("Block mined:",self.hash)Blockchain
classDiagram
class Blockchain
Blockchain : +difficulty
Blockchain : +miningReward
Blockchain : +List~Block~ chain
Blockchain : +pendingTransactions
Blockchain : +createGenBlock()
Blockchain : +getLastBlock()
Blockchain : +addBlock(newBlock)
Blockchain : +minePending(minerAddress)
Blockchain : +stageTransaction(transaction)
Blockchain : +isValid()
Blockchain : +checkBalance(address)
Blockchain : +isTransactionValid(transaction)
The Blockchain class has as attributes the difficulty of mining, the mining reward, the chain list that contains the blocks and the list of pending transactions to add to the next block. Initializing a Blockchain object creates the genesis block (createGenBlock()). The addBlock(newBlock) function adds a new block to the chain by getting the hash of the last block and mining the new block. The minePending(minnerAddress) function takes the pending transactions and adds the mining reward transaction. It then creates a new block, executes the addBlock(newBlock) function with the newly created block, and clears the list of pending transactions.
class Blockchain:
sysAddress = "0000" #The address from which the mining reward is sent
def __init__(self):
self.difficulty = 2 #The number of zeros with which the hash of the new block begins
self.miningReward = 100
self.chain = [self.createGenBlock()]
self.pendingTransactions = []
def createGenBlock(self):
genBlock = Block(str(time.time()),[Transaction(Blockchain.sysAddress,'satoshi',100)])
genBlock.prevHash = '0'
genBlock.hash = genBlock.calculateHash()
return genBlock
def getLastBlock(self):
return self.chain[-1]
def addBlock(self, newBlock):
newBlock.prevHash = self.getLastBlock().hash
newBlock.mineBlock(self.difficulty)
self.chain.append(newBlock)
def minePending(self, minerAddress):
self.pendingTransactions.append(Transaction(Blockchain.sysAddress, minerAddress, self.miningReward))
block = Block(str(time.time()), self.pendingTransactions)
self.addBlock(block)
self.pendingTransactions = []
def stageTransaction(self, transaction):
if self.isTransactionValid(transaction):
self.pendingTransactions.append(transaction)
else:
print("Transaction invalid")
def isValid(self):
for i in range(1,len(self.chain)):
if self.chain[i-1].hash != self.chain[i].prevHash:
return False
if self.chain[i].hash != self.chain[i].calculateHash():
return False
return True
def checkBalance(self, address):
balance = 0
for block in self.chain:
for trans in block.transactions:
if trans.sender == address:
balance -= trans.value
if trans.receiver == address:
balance += trans.value
return balance
def isTransactionValid(self, transaction):
if self.checkBalance(transaction.sender) < transaction.value:
return False
else:
return TrueTest
zcoin = Blockchain()
zcoin.minePending('alice')
zcoin.stageTransaction(Transaction('alice', 'bob', 25))
zcoin.minePending('bob')
print('\nBalance Alice:', zcoin.checkBalance('alice'))
print('Balance Bob:', zcoin.checkBalance('bob'))Block mined: 0042e2568d4893d988de77aec36f435e9724f4b68d8fc2c8097b61c96ebe5afe
Block mined: 00c8327f367e43fcf9d129a5f6282c2458a8baa765a92afbff3847724f6287ac
Balance Alice: 75
Balance Bob: 125
zcoin.isValid()True
Safe Blockchain
Digital signature
pip install eciespyfrom ecies import utils
from ecies import encrypt, decrypt
import ecies
def genKeyPair():
private_key = utils.generate_key()
public_key = private_key.public_key
return (private_key.to_hex(),public_key.format().hex())
def sign(data, signingKey):
k = utils.generate_key().from_hex(signingKey)
return k.sign(data.encode())
def verify(data, signature, publicKey):
try:
kpub = ecies.hex2pub(publicKey)
except:
return False
return kpub.verify(signature, data.encode())
def getPublicKey(private_key):
k = utils.generate_key().from_hex(private_key)
return k.public_key.format().hex()private_key, public_key = genKeyPair()
print('Private: ', private_key)
print('Public: ', public_key)Private: 87d8dee553a45e174be1f39d05b5a40e4f24d8aa6dfc8f8ceaa2753751bd0123
Public: 0272ec1b8fe16cec26ae254009ef1cd9cf5b3f9fdc969b419004e5aedacd511210
sig = sign('message', private_key)
print(verify('message', sig, public_key))
print(getPublicKey(private_key))True
0272ec1b8fe16cec26ae254009ef1cd9cf5b3f9fdc969b419004e5aedacd511210
Transactions
class Transaction:
def __init__(self, sender, receiver, value):
self.sender = sender
self.receiver = receiver
self.value = value
self.signature = None
def calculateHash(self):
return hs.sha256((''.join([self.sender, self.receiver, str(self.value)])).encode()).hexdigest()
def signTransaction(self, signKey):
if getPublicKey(signKey) != self.sender:
print("You cannot sign transactions for other wallets!")
hashTx = self.calculateHash()
self.signature = sign(hashTx, signKey)
def isValid(self):
if self.sender == Blockchain.sysAddress:
return True
if self.signature == None:
return False
public_key = getPublicKey(self.sender)
return verify(self.calculateHash(), self.signature, public_key)Block
class Block:
def __init__(self, timestamp, transactions):
self.timestamp = timestamp
self.transactions = transactions
self.prevHash = None
self.hash = None
self.nonce = 0
def calculateHash(self):
return hs.sha256((''.join([self.timestamp, str(self.transactions), self.prevHash, str(self.nonce)])).encode()).hexdigest()
def mineBlock(self, difficulty):
while True:
self.hash = self.calculateHash()
if self.hash[0:difficulty] == "".join(["0" for x in range(difficulty)]):
break
self.nonce += 1
print("Block mined:",self.hash)
def checkValidTransactions(self):
for tx in self.transactions:
if not tx.isValid():
return False
return True
def __str__(self):
return '\n'.join([ '|' + key + '|\t' + self.__dict__[key].__str__() +'|' for key in self.__dict__ ]) Blockchain
class Blockchain:
sysAddress = "0000"
def __init__(self):
self.difficulty = 2
self.miningReward = 100
self.chain = [self.createGenBlock()]
self.pendingTransactions = []
def createGenBlock(self):
genBlock = Block(str(time.time()),[Transaction(Blockchain.sysAddress,'satoshi',100)])
genBlock.prevHash = '0'
genBlock.hash = genBlock.calculateHash()
return genBlock
def getLastBlock(self):
return self.chain[-1]
def addBlock(self, newBlock):
newBlock.prevHash = self.getLastBlock().hash
newBlock.mineBlock(self.difficulty)
self.chain.append(newBlock)
def minePending(self, minerAddress):
self.pendingTransactions.append(Transaction(Blockchain.sysAddress, minerAddress, self.miningReward))
block = Block(str(time.time()), self.pendingTransactions)
self.addBlock(block)
self.pendingTransactions = []
def stageTransaction(self, transaction):
if self.isTransactionValid(transaction):
self.pendingTransactions.append(transaction)
else:
raise Exception("Transaction invalid")
def isValid(self):
for i in range(1,len(self.chain)):
if self.chain[i-1].hash != self.chain[i].prevHash:
return False
if self.chain[i].hash != self.chain[i].calculateHash():
return False
#Added check of valid transactions
if not self.chain[i].checkValidTransactions():
return False
return True
def checkBalance(self, address):
balance = 0
for block in self.chain:
for trans in block.transactions:
if trans.sender == address:
balance -= trans.value
if trans.receiver == address:
balance += trans.value
return balance
def isTransactionValid(self, transaction):
if self.checkBalance(transaction.sender) < transaction.value:
return False
else:
return TrueTest
myKey, myWalletAddress = genKeyPair()
AliceKey, AliceWalletAddress = genKeyPair()
BobKey, BobWalletAddress = genKeyPair()
del AliceKey, BobKey
print('My address:', myWalletAddress)
print('Alice address:', AliceWalletAddress)
print('Bob address:', BobWalletAddress)My address: 032fdc54ce91b789d9525cbfc9c8661e9f00b6cef2eb37e52a22f4687f3bedb822
Alice address: 02f6dbaf69c7a9cc53ca3a71c1936715a24cf7c7b759b01ee6be374d9935551ded
Bob address: 02f1d44e43da4034ee0dc51e9712155a11eb14081dcc0ba0bf3b5c1aed6ed90c33
zcoin = Blockchain()
zcoin.minePending(myWalletAddress)
print('My balance:',zcoin.checkBalance(myWalletAddress))Block mined: 00ff4f95dcf593f48cbe416e77bb3f78fe7f44438c79163ee1d5c8ab844285c1
My balance: 100
tx1 = Transaction(myWalletAddress, AliceWalletAddress, 50)
tx2 = Transaction(myWalletAddress, BobWalletAddress, 25)
tx1.signTransaction(myKey)
tx2.signTransaction(myKey)
zcoin.stageTransaction(tx1)
zcoin.stageTransaction(tx2)
zcoin.minePending(AliceWalletAddress)
print('My balance:',zcoin.checkBalance(myWalletAddress))Block mined: 00103396896dcc7494efa26116ca9852af780ed22987bcdea49aaf2cfed6ca3c
My balance: 25
References
- Savjee, SavjeeCoin https://github.com/Savjee/SavjeeCoin