import hashlib as hs
import time
En este post explico como programar una blockchain, especificamente una criptomoneda, desde cero en Python. Te recomiendo que le eches un vistazo a mi otro post que explica el funcionamiento de una blockchain.
Blockchain simple
Necesitamos istalar la libreria haslib
para implementar la función hash.
Terminal
pip install haslib
Importamos las librerias haslib
y time
:
Transacciones
classDiagram class Transaction Transaction : +sender Transaction : +receiver Transaction : +value
Empezaremos creando la clase Transaction
. Los objetos de este tipo recogen las propiedades de las transacciones, estas son el emisor, receptor y valor de la transacción:
class Transaction:
def __init__(self, sender, receiver, value):
self.sender = sender
self.receiver = receiver
self.value = value
Bloque
classDiagram class Block Block : +timestamp Block : +List~Transaction~ transactions Block : +prevHash Block : +hash Block : +nonce Block : +calculateHash() Block : +mineBlock(difficulty)
Definimos una clase Block
que representa cada bloque de la blockchain. Los bloques tiene como atributos: el conjunto de transacciones (transactions
), el sello temporal (timestamp
) que indica cuando se creo el bloque, el hash del bloque anterior (prevHash
), el hash del bloque (hash
) y la variable libre (nonce
). La función calculateHash()
calcula el hash del bloque usando el algoritmo SHA256 y tomando como entrada los atributos del bloque. La función mineBlock(difficulty)
ejecuta la prueba de trabajo en un bucle que incrementa la variable nonce
hasta que encuentra el hash correspondiente que comienza con el numero de ceros especificado por la dificultad de minado.
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)
La clase Blockchain
tiene como atributos la difficultad de mindao, la recompensa de minado, la lista chain que contiene los bloques y la lista de transacciones pendientes para añadir al siguiente bloque. Al inicializar un objeto Blockchain
se crea el bloque genesis (createGenBlock()
). La función addBlock(newBlock)
añade un nuevo bloque a la cadena obteniendo el hash del ultimo bloque y minando el nuevo bloque. La función minePending(minnerAddress)
coge las transacciones pendientes y añade la transacción de recompensa de minado. Posteriormente crea un nuevo bloque, ejecuta la función addBlock(newBlock)
con el nuevo bloque creado y vacia la lista de transacciones pendientes.
class Blockchain:
= "0000" #La direccion desde donde se manda la recompensa de minar
sysAddress
def __init__(self):
self.difficulty = 2 #El numero de ceros por el que empieza el hash del nuevo bloque
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
Prueba
= 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: 00d407dd89771414f4e1e82345e3b52a1caa06e0a530329f18678ef512339265
Block mined: 007e3bfeac81d55dbe56d173b6eb4b6a3d0da5ccf02d4eea71806f480ffb2248
Balance Alice: 75
Balance Bob: 125
zcoin.isValid()
True
Blockchain segura
Firma digital
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: fb0df14ef1ff94723dcd30bdb2d730a2f3aea9adaf649dc980fcb185bd72f4b4
Public: 03dccf383a774a2d7e731f074d19c0c1728192e21b86084c785da1af25f9455757
= sign('message', private_key)
sig
print(verify('message', sig, public_key))
print(getPublicKey(private_key))
True
03dccf383a774a2d7e731f074d19c0c1728192e21b86084c785da1af25f9455757
Transacciones
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)
Bloque
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
Prueba
= 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: 03a0ba1629f1e5c4bf90f6ac800cfc44626ac557968dddf37601e0da618af6a1f2
Alice address: 03dd48d41ab4bf759b1a34a52d10fe7ececd5570da95e7072395f1e228df975347
Bob address: 03c7aac89ed2668f35627ca16dabf3db3fa862be348a9a3563525b85fbd5952fed
= Blockchain()
zcoin
zcoin.minePending(myWalletAddress)print('My balance:',zcoin.checkBalance(myWalletAddress))
Block mined: 009c79fed0a87124691dc6aa983766e4b251e5f3e3386c83402df35ee8ef4d9f
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: 00ed62959aca0eb48d1631c60ff15c364b4a807433152c8c6f3cb01770a22304
My balance: 25
Referencias
- Savjee, SavjeeCoin https://github.com/Savjee/SavjeeCoin