import hashlib as hs
import time
In 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 haslib
We 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 = value
Block
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:
= "0000" #The address from which the mining reward is sent
sysAddress
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):
= Block(str(time.time()),[Transaction(Blockchain.sysAddress,'satoshi',100)])
genBlock = '0'
genBlock.prevHash hash = genBlock.calculateHash()
genBlock.return genBlock
def getLastBlock(self):
return self.chain[-1]
def addBlock(self, newBlock):
= self.getLastBlock().hash
newBlock.prevHash self.difficulty)
newBlock.mineBlock(self.chain.append(newBlock)
def minePending(self, minerAddress):
self.pendingTransactions.append(Transaction(Blockchain.sysAddress, minerAddress, self.miningReward))
= Block(str(time.time()), self.pendingTransactions)
block 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):
= 0
balance for block in self.chain:
for trans in block.transactions:
if trans.sender == address:
-= trans.value
balance if trans.receiver == address:
+= trans.value
balance return balance
def isTransactionValid(self, transaction):
if self.checkBalance(transaction.sender) < transaction.value:
return False
else:
return True
Test
= Blockchain()
zcoin
'alice')
zcoin.minePending(
'alice', 'bob', 25))
zcoin.stageTransaction(Transaction(
'bob')
zcoin.minePending(
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 eciespy
from ecies import utils
from ecies import encrypt, decrypt
import ecies
def genKeyPair():
= utils.generate_key()
private_key = private_key.public_key
public_key return (private_key.to_hex(),public_key.format().hex())
def sign(data, signingKey):
= utils.generate_key().from_hex(signingKey)
k return k.sign(data.encode())
def verify(data, signature, publicKey):
try:
= ecies.hex2pub(publicKey)
kpub except:
return False
return kpub.verify(signature, data.encode())
def getPublicKey(private_key):
= utils.generate_key().from_hex(private_key)
k return k.public_key.format().hex()
= genKeyPair()
private_key, public_key
print('Private: ', private_key)
print('Public: ', public_key)
Private: 87d8dee553a45e174be1f39d05b5a40e4f24d8aa6dfc8f8ceaa2753751bd0123
Public: 0272ec1b8fe16cec26ae254009ef1cd9cf5b3f9fdc969b419004e5aedacd511210
= sign('message', private_key)
sig
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!")
= self.calculateHash()
hashTx self.signature = sign(hashTx, signKey)
def isValid(self):
if self.sender == Blockchain.sysAddress:
return True
if self.signature == None:
return False
= getPublicKey(self.sender)
public_key
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:
= "0000"
sysAddress
def __init__(self):
self.difficulty = 2
self.miningReward = 100
self.chain = [self.createGenBlock()]
self.pendingTransactions = []
def createGenBlock(self):
= Block(str(time.time()),[Transaction(Blockchain.sysAddress,'satoshi',100)])
genBlock = '0'
genBlock.prevHash hash = genBlock.calculateHash()
genBlock.return genBlock
def getLastBlock(self):
return self.chain[-1]
def addBlock(self, newBlock):
= self.getLastBlock().hash
newBlock.prevHash self.difficulty)
newBlock.mineBlock(self.chain.append(newBlock)
def minePending(self, minerAddress):
self.pendingTransactions.append(Transaction(Blockchain.sysAddress, minerAddress, self.miningReward))
= Block(str(time.time()), self.pendingTransactions)
block 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):
= 0
balance for block in self.chain:
for trans in block.transactions:
if trans.sender == address:
-= trans.value
balance if trans.receiver == address:
+= trans.value
balance return balance
def isTransactionValid(self, transaction):
if self.checkBalance(transaction.sender) < transaction.value:
return False
else:
return True
Test
= genKeyPair()
myKey, myWalletAddress
= genKeyPair()
AliceKey, AliceWalletAddress = genKeyPair()
BobKey, BobWalletAddress
del AliceKey, BobKey
print('My address:', myWalletAddress)
print('Alice address:', AliceWalletAddress)
print('Bob address:', BobWalletAddress)
My address: 032fdc54ce91b789d9525cbfc9c8661e9f00b6cef2eb37e52a22f4687f3bedb822
Alice address: 02f6dbaf69c7a9cc53ca3a71c1936715a24cf7c7b759b01ee6be374d9935551ded
Bob address: 02f1d44e43da4034ee0dc51e9712155a11eb14081dcc0ba0bf3b5c1aed6ed90c33
= Blockchain()
zcoin
zcoin.minePending(myWalletAddress)print('My balance:',zcoin.checkBalance(myWalletAddress))
Block mined: 00ff4f95dcf593f48cbe416e77bb3f78fe7f44438c79163ee1d5c8ab844285c1
My balance: 100
= Transaction(myWalletAddress, AliceWalletAddress, 50)
tx1 = Transaction(myWalletAddress, BobWalletAddress, 25)
tx2
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