Creación de una criptomoneda (1º Parte)

Blockchain

Las criptomonedas están revolucionando el mundo financiero y prometen cambiar completamente la forma en la que se realizan las transacciones económicas en el futuro. Esta revolución se inicio con la aparición del Bitcoin en año 2009 y la tecnología Blockchain (o cadenas de bloques).

En esta serie de artículos se va a crear una criptomoneda desde cero en Python con la finalidad de comprender el funcionamiento de esta tecnología. El código actualizado se puede encontrar en el repositorio de github.

Cadena de bloques

Una cadena de bloques o Blockchain es una base de datos en la que se almacena registros en forma de lista ordenada. Una vez agregado un registro en estas bases de datos no pueden ser modificados sin alterara completamente la cadena de bloques desde la posición en la que fue agregado este. Gracias a esta característica la tecnología Blockchain permite crear libros de registro distribuidos que pueden ser utilizados para implementar libros de contabilidad y crear criptomonedas.

Esquema de una cadena de bloques

Bloque

La estructura básica en la que se basa la tecnología Blockchain es el bloque. El bloque es la unida básica de información que se puede almacenar en la base de datos. Una estructura de bloques básica ha de contener los siguientes campos

  • Índice (index): contiene la posición del bloque en la cadena de bloques
  • Hash anterior (previous_hash): el hash del anterior bloque
  • Fecha (timestamp): la fecha en la que se ha generado el bloque
  • Datos (data): los datos que almacena el bloque
  • Hash (hash): el hash del propio bloque

En Python la estructura básica de un bloque se puede crear con el constructor:

class Block:
	def __init__(self, index, data, previous_hash, timestamp):
		self.index = index
		self.data = data
		self.previous_hash = previous_hash
		self.timestamp = timestamp

		self.hash = self.calculate_hash()

	def calculate_hash(self):
		return None

El método calculate_hash se implementará mas adelante la funcionalidad que permitirá calcular el hash de cada uno de los bloques de la cadena.

En este punto es importante notar que cada uno de los bloques contiene el hash propio y el del anterior en la cadena. De esta forma se puede comprobar la integridad de la cadena. A partir de este punto se puede crear una clase BlockChain que permita agregar elementos a la cadena y validar la integridad de esta:

class BlockChain:
	def __init__(self, block):
    	self.chain = [block]</p>
	
	def add_block(self, block):
    	self.chain.append(block)</p>
	
	def is_valid(self):
    	if self.chain:
    		for step in range(len(self.chain) - 1, 0, -1):
        		if self.chain[step].previous_hash != self.chain[step - 1].hash:
          			return False
        		elif self.chain[step].index != self.chain[step - 1].index + 1:
          			return False
            
            return True
        return False

La clase BlockChain solamente necesita un vector con los bloques, un método que permita agregar nuevos bloques (add_block) y una función que permita validar la integridad de la cadena (is_valid). Este pequeño código ya permite validar cadenas de bloques de forma sencilla:

blockchain = BlockChain(Block(0, None, None, None))
blockchain.add_block(Block(1, None, None, None))

assert blockchain.is_valid()

En el caso de falte un elemento en la cadena

blockchain.add_block(Block(4, None, None, None))

assert blockchain.is_valid() is False

Para mejorar la seguridad de la cadena de bloques es necesario introducir las funciones de hash que se explicarán a continuación

Funciones de Hash

Los hashes son algoritmos matemáticos que a partir de una entrada de datos generan una salida, generalmente de longitud fija, que representan un resumen de la información que se le ha suministrado. Una mínima modificación de los datos de entrada provoca que el resultado sea completamente diferente. Además, a partir del hash no es posible obtener los datos que han generado este ya que es un resumen de los mismo. El coste computacional para calcular estas funciones es bajo, por lo que se utilizan para comprobar la integridad de conjuntos de datos. Existen mutiples funciones de hash, entre las más utilizadas se pueden destacar el MD5 y la familia SHA-2.

Estas propiedades se pueden observar simplemente calculando funciones de hash para diferentes cadenas de texto en las que se ha modificado ligeramente el contenido de esta. Por ejemplo, se puede observar el resultado de utilizar el algoritmo SHA-256 a las cadenas de texto “Hello.” y “Hello!” en las que únicamente se ha cambiado el punto por el signo de admiración.

from hashlib import sha256

print('Hello. -', sha256("Hello.".encode('utf-8')).hexdigest())
print('Hello! -', sha256("hello!".encode('utf-8')).hexdigest())

lo que genera el resultado:

Hello. - 2d8bd7d9bb5f85ba643f0110d50cb506a1fe439e769a22503193ea6046bb87f7
Hello! - ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b

En este caso se puede observar como un pequeño cambio en la entrada ha modificado completamente el resultado obtenido.

Existen múltiples aplicaciones para las funciones de hash. Por ejemplo, en el envío de archivos a través de intentar son utilizadas para garantizar que los datos originales no sufran modificaciones. En el caso de que exista un error en la transferencia de los datos el hash del archivo recibido cambiaria completamente, lo mismo si un atacante desea modificar el contenido.

El hash en el bloque

El hash de los bloques es la propiedad más importante de los mismos. Es lo que permite garantizar que una vez agregado un bloque este no puede ser modificado sin que sea detectado. El hash se calcula utilizando todos los datos del bloque. Esto significa se modifica cualquier cosa el hash del bloque original ya no será valido. En nuestro ejemplo la implementación del método calculate_hash se puede realizar con las siguientes líneas de código

def calculate_hash(self):
  block = [self.index, self.previous_hash, self.timestamp, self.data]
  return sha256(repr(block).encode('utf-8')).hexdigest()

Ahora se puede crear un bloque y comprobar como se calcula el valor del hash. Por ejemplo el hash del un bloque Block(0, None, None, None) será:

 4e78e3a3f2df4fcfc7bc61e4af0658c6fe9ae46465e0b4ce65bd5c5308e97640

Mientras que el siguiente elemento de la cadena será:

9ed832e836abcb2dc9ea277923a211eea50e7c8ea23387d0c382f04e9545c149

A través de un ejemplo se puede comprobar el funcionamiento de las cadenas de bloques y comprender mejor porque no pueden ser modificadas. Por ejemplo, si se dejase crear una lista de frutas simplemente se ha de ejecutar el siguiente código

data = ["Avocado", "Apple", "Cherry", "Orange", "Strawberry"]

blockchain = BlockChain(Block(0, data[0], None, None))

for i in range(1, len(data)):
    blockchain.add_block(Block(i, data[i], blockchain.chain[i-1].hash, None))

assert blockchain.is_valid()

En estos momentos blockchain contiene una cadena de bloques valida. Si alguien desea modificar “Cherry” por “Khaki” a la hora de validar la cadena se obtendrá un mensaje de que la cadena no es valida y, por lo tanto, se puede deducir que ha sido modificada:

blockchain.chain[2] = Block(2, 'Khaki', blockchain.chain[1].hash, None)
assert blockchain.is_valid() is False

Imprimiendo el contenido de la cadena de bloques se puede claramente el bloque que ha sido modificada. En segunda cadena se puede ver que el valor del hash previo del bloque 3 no coincide con el obtenido en el bloque 2.

Block: 0 (None)
 Hash: df124b19b548a09aaedc0d22cc5d663b40fe3db606ebcc309bec95f8b4b1dbf7
 Previous: None
Block: 1 (None)
 Hash: 97a535ef5a9a880a543c45dc750a0f4b67a9877d1c46fb817ce9a3689cc5e933
 Previous: df124b19b548a09aaedc0d22cc5d663b40fe3db606ebcc309bec95f8b4b1dbf7
Block: 2 (None)
 Hash: 4af432680e86c0d3565e68117c684d49d600b4e28a8558b822385beaed524031
 Previous: 97a535ef5a9a880a543c45dc750a0f4b67a9877d1c46fb817ce9a3689cc5e933
Block: 3 (None)
 Hash: 695ae8d09e57f053f7f9bd5cb33ac95b8fd9770236f30a3fa32a5c0ae26e8f7d
 Previous: abc57d0f4ae2edb832af7950d8c8abe350ab26f5e98331a9dbd62f97f5aad835
Block: 4 (None)
 Hash: 2c5109b415a6791dcfae720978a995bdc9a95de6b963637aa323b19fa37debdf
 Previous: 695ae8d09e57f053f7f9bd5cb33ac95b8fd9770236f30a3fa32a5c0ae26e8f7d

En el caso de que el atacante quiera modificar el contenido de un bloque, para evitar ser detectado, tendrá que recalcular y actualizar el hash de todos los bloques. En caso contrario, comprobando los hashes de la cadena se podrá observar fácilmente que esta ha sido alterada y donde.

En la siguiente entrada de la serie se explicará el concepto de prueba de esfuerzo.

Imágenes: Pixabay (mmi9)

Sin votos
Por favor espera...

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *