import hashlib as hs
import timeEn 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 haslibImportamos 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 = valueBloque
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:
sysAddress = "0000" #La direccion desde donde se manda la recompensa de minar
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):
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 TruePrueba
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: 00d407dd89771414f4e1e82345e3b52a1caa06e0a530329f18678ef512339265
Block mined: 007e3bfeac81d55dbe56d173b6eb4b6a3d0da5ccf02d4eea71806f480ffb2248
Balance Alice: 75
Balance Bob: 125
zcoin.isValid()True
Blockchain segura
Firma digital
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: fb0df14ef1ff94723dcd30bdb2d730a2f3aea9adaf649dc980fcb185bd72f4b4
Public: 03dccf383a774a2d7e731f074d19c0c1728192e21b86084c785da1af25f9455757
sig = sign('message', private_key)
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!")
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)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:
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 TruePrueba
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: 03a0ba1629f1e5c4bf90f6ac800cfc44626ac557968dddf37601e0da618af6a1f2
Alice address: 03dd48d41ab4bf759b1a34a52d10fe7ececd5570da95e7072395f1e228df975347
Bob address: 03c7aac89ed2668f35627ca16dabf3db3fa862be348a9a3563525b85fbd5952fed
zcoin = Blockchain()
zcoin.minePending(myWalletAddress)
print('My balance:',zcoin.checkBalance(myWalletAddress))Block mined: 009c79fed0a87124691dc6aa983766e4b251e5f3e3386c83402df35ee8ef4d9f
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: 00ed62959aca0eb48d1631c60ff15c364b4a807433152c8c6f3cb01770a22304
My balance: 25
Referencias
- Savjee, SavjeeCoin https://github.com/Savjee/SavjeeCoin