Con la burbuja experimentada por el Bitcoin en 2017, y su posterior desplome en 2018, se ha despertado interés por la tecnología subyacente, el blockchain. Sus defensores afirman que es revolucionaria, con aplicaciones que van más allá de facilitar pagos. Para desmitificar este concepto y facilitar las discusiones acerca de este tema, acá se explica de manera sencilla, con ayuda de Python, qué es y cómo funciona el blockchain.
Más allá de la discutible utilidad del Bitcoin como dinero, genera interés el blockchain o cadena de bloques y el distributed ledger o libro mayor distribuido.
El distributed ledger fue concebido como un mecanismo para descentralizar los registros de valor de las instituciones financieras, que es indispensable para la operación de los sistemas de pagos. Por ejemplo, cuando Ariel desea pagarle a María a través de una transferencia bancaria, es el banco que en sus registros contables anota una disminución en los activos de Ariel y un aumento en los de María.
El registro centralizado de valor es vulnerable a ataques informáticos, pero se ha sido visto necesario porque era difícil resolver el problema del “doble gasto” en una red descentralizada. Esto es que una persona pudiese gastar más de una vez el mismo valor aprovechándose de la descoordinación o asincronismo entre los distintos registros.
En medio de la Gran Recesión de 2008, se publicó un artículo bajo el nombre de Satoshi Nakamoto (2008) que describe una forma de resolver este problema: el blockchain.
Un blockchain es un registro de transacciones fragmentando en bloques secuenciales de información, vinculados entre sí por medio de algoritmos cartográficos. Estas cadenas de bloques pueden ser compartidas entre muchos usuarios, quienes posiblemente no se conozcan entre sí, a través de una red peer-to-peer. Cuando se produce una transacción, esta es registrada en el bloque más reciente de la cadena casi simultáneamente por numerosos nodos de esta red descentralizada, que resuelve el problema de doble gasto por medio del consenso: dar por válidas las transacciones que están incluidas en nodos que constituyen una mayoría.
Aquí se muestra versión muy simple de un código para llevar a cabo transacciones en cococoins. La intención es explicar de manera sencilla qué es un blockchain. Para esto se dejan de lado algunas cuestiones técnicas de una red de pagos, como la autenticación de usuarios, el protocolo de comunicación, y la distribución de registros contables.
Para empezar, se importan algunos paquetes de Python:
from hashlib import sha3_256
from datetime import datetime
import numpy as np, pandas as pd
from random import randint, sample
La función hashlib.sha3_256
se requiere para encriptar información, datetime
contiene la función now
para anotar la fecha y hora de las transacciones, pandas
facilita almacenar los datos, numpy
y random
serán necesarios para hacer simulaciones.
El primer paso es encriptar la información. sha3_256
es la función hash más reciente (2015) de la familia SHA (Secure Hash Algorithm), publicada por el Instituto Nacional de Normas y Tecnología de Estados Unidos.
Una función hash es un algoritmo que toma un texto de cualquier tamaño y lo transforma en un número hexadecimal (expresado con los dígitos 0-9 y las letras A-F) de un tamaño predeterminado, que cumple:
Para que se cumpla la última condición es indispensable que el número de potenciales textos a encriptar sea menor que el número de hashs.
El tamaño del hash depende del nivel de seguridad deseado. Por ejemplo, sha3_256
da un resultado de 256 bits, con el cual se representa un número hexadecimal de 64 dígitos (cada número hexadecimal requiere 4 bits para almacenarse).
64 dígitos hexadecimales parecen pocos para encriptar todos los posibles textos, pero puede encriptarse $16^{64}\approx 1.16\times 10^{77}$ textos distintos, un número muy superior al número de estrellas del universo veces el número de granos de arena en el planeta (10.000.000.000.000.000.000.000 de estrellas en el universo 4.000.000.000.000.000.000.000 granos de arena en el mundo)...
A continuación, se define la función encriptar
, que toma una frase
y la guarda en formato unicode (.encode
), lo encripta con sha3_256
, y lo representa en hexadecimal (.hexdigest
). Para facilidad de lectura, este número se escribe en mayúscula (.upper
) y se parte en secuencias de cuatro dígitos.
def encriptar(frase):
encriptado = sha3_256(frase.encode('utf-8')).hexdigest().upper()
return ' '.join([encriptado[i:i + 4] for i in range(0, 64, 4)])
Para probar esta función, se encripta "Bad Guy"
, lo cual da por resultado este código de 64 dígitos:
encriptar('Bad Guy')
'2B71 E505 3B0D 6E8D 4A01 7893 91E4 6DFC F7EC 7160 CABA 81AC 19EE 12FC 3DE8 5B31'
Para ilustrar las propiedades de la función hash, se ve cómo cambia ese código ante pequeños cambios en el texto:
encriptar('bad guy')
'A1F1 EAB7 E4BE D26F DFDB 5B4C FB2A E16A 8D6C 5E90 3D20 52B5 0F58 AD16 465C 68D9'
¿Y si se omite el espacio?
encriptar('Badguy')
'AFDD 2429 A72E 34CA E713 51C5 A1E2 3CE4 1041 BF2A C729 85B4 C665 8FDE B452 1355'
Por lo tanto, pequeños cambios en el texto da por resultado códigos completamente distintos. Conocer los códigos de algunos textos es inútil para adivinar el mensaje encriptado en otro código.
Para hacer una cadena de bloques primero se necesita construir los bloques individuales. Cada bloque contiene datos, y la cadena de bloques es una secuencia ordenada de bloques de información por medio de claves criptográficas.
A continuación, se programa una clase llamada Bloque
: un prototipo de bloque en el que se describe qué datos contendrá y qué acciones podrá hacer con ellos. Posteriormente se crean los bloques individuales (objetos) de la cadena a partir de este prototipo.
class Bloque:
def __init__(self, índice, saldos, código_previo):
self.índice = índice
self.saldos = saldos
self.código_previo = código_previo
self.código = None
self.transacciones = pd.DataFrame(columns=['Fecha', 'De', 'Para', 'Cantidad'])
self.fecha_hora = datetime.now()
def encriptar_bloque(self):
datos = ['índice', 'saldos', 'código_previo', 'transacciones', 'fecha_hora']
contenido = ''.join(str(getattr(self,campo)) for campo in datos)
self.código = encriptar(contenido)
def actualizar_transacciones(self, de, para, cantidad):
T = self.transacciones.shape[0]
self.transacciones.loc[T] = [datetime.now(), de, para, cantidad]
Bloque
es una caja con información. Esto se ilustra en la figura 1, donde los datos contenidos en el bloque se muestran en cuadros rosados, y las acciones en cuadros celestes en la parte baja del bloque.
Figura 1: Clase Bloque
__init__
se utiliza para indicarle a Python cómo crear o “iniciar” un nuevo objeto. En este caso, para crear un Bloque
nuevo se requiere de tres datos: índice, saldos, código_previo
, que simplemente son almacenados en el bloque.
índice
: un número entero que indica la posición del bloque en la cadena.saldos
: una serie de pandas
que registrará las tenencias de cococoins de cada usuario.código_previo
el código criptográfico del bloque anterior en la cadena, que servirá para enlazar los bloques y para determinar si la cadena es modificada “ilegalmente”.Aparte de estos tres datos, esta clase indica que cada bloque tendrá:
código
: el código criptográfico del bloque (inicialmente ninguno), que servirá para encriptar los datos del bloque.transacciones
: una tabla de datos de pandas
(inicialmente vacía) que registrará detalles de 'Fecha', 'De', 'Para', 'Cantidad'
para cada uno de los pagos realizados con cococoins. fecha_hora
: la fecha y hora en que se creó el bloque, obtenido de datetime.now
.Además, la clase define dos métodos o acciones que puede realizar un bloque:
encriptar_bloque
: encripta toda la información del bloque. Se define datos
como una lista de los componentes del bloque que serán encriptados: 'índice'
, 'saldos'
, 'código_previo'
, 'transacciones'
, y 'fecha_hora'
. Desupués se obtiene el valor de cada uno de esos componentes (con getattr
), se convierte a texto (con str
), y luego se concatena (.join
). Finalmente, se utiliza encriptar
y se guarda el resultado en el campo código
del bloque.
actualizar_transacciones
: registra nuevos pagos con la cadena de bloques. Esta función tiene tres insumos (i) de
=cuenta origen, (ii) para
=cuenta destino, y (iii) cantidad
=número de cococoins que se está pagando. Estos datos, junto con la fecha y hora actual (datetime.now
) son almacenados al final de la tabla de datos transacciones
del bloque.
Para diseñar la cadena de bloques se crea la clase que hereda de list
, lo que quiere decir que la cadena de bloques será similar a una lista pero con características especiales. Al igual que en el diseño de Bloque
, estas características serán los datos que contiene la cadena (que se definen con @property
) y las acciones que puede ejecutar la cadena.
class BlockChain(list):
def __init__(self):
saldo_original = pd.Series([0], index=['Banco Central'])
bloque_original = Bloque(0, saldo_original, None)
self.append(bloque_original)
@property
def bloque_actual(self):
return self[-1]
@property
def saldos(self):
return self.bloque_actual.saldos
@property
def transacciones(self):
return pd.concat([bloque.transacciones for bloque in self],
keys=[f'Bloque{k}' for k in range(len(self))])
def emitir_cococoins(self, cantidad):
if cantidad < -self.bloque_actual.saldos['Banco Central']:
print("No pueden destruirse más cococoins que los que posee el Banco Central")
else:
self.bloque_actual.saldos['Banco Central'] += cantidad
self.bloque_actual.actualizar_transacciones('Imprimiendo nuevos cococoins', 'Banco Central', cantidad)
def pagar(self, cuenta_origen, cuenta_destino, cantidad):
cb = self.bloque_actual
if cantidad < 0:
print("El monto del pago no puede ser negativo.")
elif cb.saldos[cuenta_origen] < cantidad:
print(f"La cuenta {cuenta_origen} no tiene fondos suficientes!")
else:
if cuenta_destino not in cb.saldos.keys():
cb.saldos[cuenta_destino] = 0
cb.saldos[cuenta_origen] -= cantidad
cb.saldos[cuenta_destino] += cantidad
cb.actualizar_transacciones(cuenta_origen, cuenta_destino, cantidad)
msg = '%12s le pagó %6.2f cococoins a %s.'
print(msg % (cuenta_origen, cantidad, cuenta_destino))
def crear_siguiente_bloque(self):
cb = self.bloque_actual
cb.encriptar_bloque()
self.append(Bloque(cb.índice + 1, cb.saldos.copy(), cb.código))
def verificar_integridad(self):
anterior = self.bloque_actual.código_previo
for bloque in self[-2::-1]:
print(f'\nVerificando bloque{bloque.índice}', anterior, sep='\n')
bloque.encriptar_bloque()
print(bloque.código)
if bloque.código != anterior:
print('ADVERTENCIA: LA CADENA DE BLOQUES FUE ADULTERADA EN EL BLOQUE %d!!!' % bloque.índice)
return
else:
anterior = bloque.código_previo
print('LA CADENA DE BLOQUES ESTÁ BIEN!')
def nuevo_desafio(self, p):
code = randint(0,10**p - 1)
self.desafio = encriptar(str(code))
print(f'\nNuevo desafío (dificultad = {p}): {self.desafio}')
def verificar_solucion(self, propuesta, proponente):
if encriptar(str(propuesta)) == self.desafio:
COCOCOIN.emitir_cococoins(20)
self.pagar('Banco Central', proponente, 20)
self.crear_siguiente_bloque()
self.nuevo_desafio(p)
Figura 2: Clase BlockChain
De nuevo, __init__
indica cómo Python debe crear o “iniciar” una nueva cadena de bloques. En este caso se crea un Bloque
inicial, el bloque “génesis”.
Para crear un Bloque
nuevo se necesita: índice, saldos, código_previo
. Por tratarse del primer bloque, se le asigna el índice 0, con un saldo original de 0 a favor del 'Banco Central'
, y ningún (None
) código anterior por no haber un bloque antes del inicial. Este primer bloque se agrega (.append
) al final de la cadena, que hasta ahora estaba vacía.
Aparte de los bloques, la cadena contiene tres propiedades o datos:
bloque_actual
: el último bloque en la cadena, en cual se indexa como -1
. Este es el único en el que se registrarán transacciones nuevas.saldos
: son los datos más actualizados de tenencias de cococoins de cada usuario, el campo saldos
del bloque_actual
.transacciones
: todas las transacciones registradas a lo largo de la cadena, concatenando (pd.concat
) el campo transacciones
de todos los bloques (for bloque in self
) de la cadena, indexándolos además (keys=
) por el número de bloque ('Bloque{k}'
) en el cual están registrados.Blockchain
define seis acciones que puede hacer la cadena:
emitir_cococoins
para poder pagar con cococoins alguien debe emitirlos con anterioridad. Para simplificar, se asume que hay un 'Banco Central'
que cumple esta función, a diferencia de lo que sucede con Bitcoin y otros criptoactivos en los cuales se “minan” las nuevas monedas. Se indica que el único parámetro requerido es la cantidad
, que de ser válida se le suma al saldo del 'Banco Central'
y queda constancia al actualizar la tabla de transacciones. Todo ello en el bloque_actual
de la cadena. No obstante, la operación no será válida si pretende sacar de circulación (cantidad
negativa) más cococoins que los que existen en el 'Banco Central'
; de ser así, se imprime una advertencia.
pagar
es la pieza más importante de la plantilla; la finalidad de Blockchain
es constituirse en un sistema de pagos. Se especifica que para tramitar un pago la cadena necesita saber quién (cuenta_origen
) le paga cuántos cococoins (cantidad
) a quién (cuenta_destino
). Con esa información, Blockchain
crea un “atajo” para el bloque actual, llamándolo cb
y verifica dos cosas: (i) que la cantidad no sea negativa , porque sería un cobro en vez de un pago, y (ii) que la cuenta_origen
tenga fondos suficientes. De ser así, se verifica que cuenta_destino
ya tenga una cuenta abierta (de lo contrario se abre una con saldo 0), y se procede a debitar cantidad
del saldo de cuenta_origen
y a acreditar esos fondos a cuenta_destino
. Finalmente, se registra la transacción y se imprime un mensaje de confirmación.
crear_siguiente_bloque
es un mecanismo para crear un Bloque
nuevo y enlazarlo en la cadena. Para ello, se encripta el bloque_actual
y se añade (.append
) un Bloque
nuevo: (i) su índice
es igual al índice del bloque actual más 1 (cb.índice+1
), (ii) tiene como saldos
iniciales una copia de los saldos actuales (cb.saldos.copy()
), y (iii) en código_anterior
guarda el valor encriptado del bloque actual (cb.código
).
verificar_integridad
una de las novedades del “blockchain” es que consiste en un mecanismo que permite a la vez fragmentar toda la información de la cadena en bloques individuales y detectar si alguno de los bloques ha sido adulterado. En este método se verifica si la cadena ha sido adulterada utilizando los código de encriptación cada vez que se crean bloques nuevos por medio de crear_siguiente_bloque
. El mecanismo es muy sencillo: se itera la cadena hacia atrás, revisando que lo que un bloque señala como el código_previo
sigue correspondiendo con la información encriptada del bloque anterior; si no es así, sale una advertencia.
La descrición de los métodos nuevo_desafio
y verificar_solucion
queda para la sección 5, cuando se discute una forma de “minar” cococoins.
Se empieza creando el blockchain de COCOCOIN
, emitiendo los primero 5000 cococoins, y revisando los saldos de la cadena:
COCOCOIN = BlockChain()
COCOCOIN.emitir_cococoins(5000)
COCOCOIN.saldos
Banco Central 5000 dtype: int64
El único saldo hasta ahora corresponde a la emisión inicial de cococoins.
Ahora, el 'Banco Central'
le paga 600 cococoins a cada colaborador 'Tamar', 'Esteban', 'Jordy', 'Daniela'
for nombre in ['Tamar', 'Esteban', 'Jordy', 'Daniela']:
COCOCOIN.pagar('Banco Central', nombre, 600)
Banco Central le pagó 600.00 cococoins a Tamar. Banco Central le pagó 600.00 cococoins a Esteban. Banco Central le pagó 600.00 cococoins a Jordy. Banco Central le pagó 600.00 cococoins a Daniela.
Revisando los saldos:
COCOCOIN.saldos
Banco Central 2600 Tamar 600 Esteban 600 Jordy 600 Daniela 600 dtype: int64
y el registro de transacciones:
COCOCOIN.transacciones
Fecha | De | Para | Cantidad | ||
---|---|---|---|---|---|
Bloque0 | 0 | 2023-06-01 21:53:33.670690 | Imprimiendo nuevos cococoins | Banco Central | 5000 |
1 | 2023-06-01 21:53:33.679775 | Banco Central | Tamar | 600 | |
2 | 2023-06-01 21:53:33.685790 | Banco Central | Esteban | 600 | |
3 | 2023-06-01 21:53:33.692112 | Banco Central | Jordy | 600 | |
4 | 2023-06-01 21:53:33.695619 | Banco Central | Daniela | 600 |
Ahora se simulan pagos aleatorios para crear 5 nuevos bloques, cada uno de los cuales tendrá un NÚMERO_DE_PAGOS
aleatorio entre 3 y 6. Para cada uno de esos pagos, se eligen dos cuentas al azar, así como una cantidad aleatoria de entre 10 y 110 cococoins. El pago se procesa en la última línea:
np.random.seed(2023)
NUMERO_DE_BLOQUES = 5
for i in range(0, NUMERO_DE_BLOQUES):
COCOCOIN.crear_siguiente_bloque()
print('\n', '='*60)
print(f"El código del bloque {COCOCOIN[-2].índice} es:\n{COCOCOIN[-2].código}")
print(f"El bloque #{COCOCOIN.bloque_actual.índice} ha sido agregado a la cadena del Cocoin!")
NÚMERO_DE_PAGOS = np.random.randint(3,7)
for k in range(NÚMERO_DE_PAGOS):
DE, PARA = sample(list(COCOCOIN.saldos.index), 2)
CANTIDAD = 10 * np.random.randint(1, 12)
COCOCOIN.pagar(DE, PARA, CANTIDAD)
============================================================ El código del bloque 0 es: 2392 3C50 00C8 A67A 9E97 E205 C297 447B 0CBA 24E3 DF48 0136 1D5B DACB D1D9 4E60 El bloque #1 ha sido agregado a la cadena del Cocoin! Tamar le pagó 100.00 cococoins a Daniela. Banco Central le pagó 70.00 cococoins a Esteban. Daniela le pagó 80.00 cococoins a Banco Central. Esteban le pagó 20.00 cococoins a Daniela. Tamar le pagó 40.00 cococoins a Jordy. Jordy le pagó 50.00 cococoins a Daniela. ============================================================ El código del bloque 1 es: F559 3EC7 AC2E 3B0D 8E3C 93B9 2277 A436 2F99 4EB6 C2F6 0C9E E1DD 1078 0545 384C El bloque #2 ha sido agregado a la cadena del Cocoin! Tamar le pagó 70.00 cococoins a Banco Central. Banco Central le pagó 60.00 cococoins a Daniela. Esteban le pagó 10.00 cococoins a Daniela. ============================================================ El código del bloque 2 es: CC12 2115 4C6C 2442 9DA5 A251 92F3 66FF 04B7 23BE 1F67 70A7 B636 EE2C 7311 A18A El bloque #3 ha sido agregado a la cadena del Cocoin! Banco Central le pagó 20.00 cococoins a Jordy. Tamar le pagó 60.00 cococoins a Daniela. Jordy le pagó 80.00 cococoins a Banco Central. Jordy le pagó 60.00 cococoins a Esteban. Daniela le pagó 90.00 cococoins a Banco Central. ============================================================ El código del bloque 3 es: 75F3 7882 FD3F 557B 794B 888F 329F DAD9 222B B602 74D5 6293 09C9 8BC2 5AE0 D834 El bloque #4 ha sido agregado a la cadena del Cocoin! Jordy le pagó 40.00 cococoins a Tamar. Daniela le pagó 110.00 cococoins a Banco Central. Esteban le pagó 20.00 cococoins a Tamar. Esteban le pagó 10.00 cococoins a Tamar. Banco Central le pagó 80.00 cococoins a Daniela. ============================================================ El código del bloque 4 es: 535E CE98 8FB0 07AC 7285 EAEA 7FC7 3602 3B0B 7B59 9A06 DB34 7F70 5913 5039 D03E El bloque #5 ha sido agregado a la cadena del Cocoin! Tamar le pagó 20.00 cococoins a Daniela. Daniela le pagó 10.00 cococoins a Jordy. Daniela le pagó 110.00 cococoins a Banco Central. Daniela le pagó 10.00 cococoins a Tamar. Daniela le pagó 20.00 cococoins a Banco Central. Banco Central le pagó 90.00 cococoins a Jordy.
Hay una semilla para obtener los mismos resultados cada vez (para poder reproducir los resultados, aunque los hash serán distintos porque cambia la fecha y hora de ejecución del código).
Se revisan de nuevo los saldos, y se ve de los cuatro participantes que:
COCOCOIN.saldos
Banco Central 2840 Tamar 390 Esteban 670 Jordy 530 Daniela 570 dtype: int64
Ahora se imprime la tabla completa de transacciones, y se comprueba que su detalle es consistente con la bitácora que quedó impresa más arriba:
COCOCOIN.transacciones
Fecha | De | Para | Cantidad | ||
---|---|---|---|---|---|
Bloque0 | 0 | 2023-06-01 21:53:33.670690 | Imprimiendo nuevos cococoins | Banco Central | 5000 |
1 | 2023-06-01 21:53:33.679775 | Banco Central | Tamar | 600 | |
2 | 2023-06-01 21:53:33.685790 | Banco Central | Esteban | 600 | |
3 | 2023-06-01 21:53:33.692112 | Banco Central | Jordy | 600 | |
4 | 2023-06-01 21:53:33.695619 | Banco Central | Daniela | 600 | |
Bloque1 | 0 | 2023-06-01 21:53:33.761097 | Tamar | Daniela | 100 |
1 | 2023-06-01 21:53:33.774607 | Banco Central | Esteban | 70 | |
2 | 2023-06-01 21:53:33.775716 | Daniela | Banco Central | 80 | |
3 | 2023-06-01 21:53:33.775716 | Esteban | Daniela | 20 | |
4 | 2023-06-01 21:53:33.775716 | Tamar | Jordy | 40 | |
5 | 2023-06-01 21:53:33.775716 | Jordy | Daniela | 50 | |
Bloque2 | 0 | 2023-06-01 21:53:33.785734 | Tamar | Banco Central | 70 |
1 | 2023-06-01 21:53:33.790249 | Banco Central | Daniela | 60 | |
2 | 2023-06-01 21:53:33.790249 | Esteban | Daniela | 10 | |
Bloque3 | 0 | 2023-06-01 21:53:33.790249 | Banco Central | Jordy | 20 |
1 | 2023-06-01 21:53:33.790249 | Tamar | Daniela | 60 | |
2 | 2023-06-01 21:53:33.790249 | Jordy | Banco Central | 80 | |
3 | 2023-06-01 21:53:33.790249 | Jordy | Esteban | 60 | |
4 | 2023-06-01 21:53:33.806333 | Daniela | Banco Central | 90 | |
Bloque4 | 0 | 2023-06-01 21:53:33.808670 | Jordy | Tamar | 40 |
1 | 2023-06-01 21:53:33.808670 | Daniela | Banco Central | 110 | |
2 | 2023-06-01 21:53:33.808670 | Esteban | Tamar | 20 | |
3 | 2023-06-01 21:53:33.808670 | Esteban | Tamar | 10 | |
4 | 2023-06-01 21:53:33.822202 | Banco Central | Daniela | 80 | |
Bloque5 | 0 | 2023-06-01 21:53:33.822202 | Tamar | Daniela | 20 |
1 | 2023-06-01 21:53:33.822202 | Daniela | Jordy | 10 | |
2 | 2023-06-01 21:53:33.822202 | Daniela | Banco Central | 110 | |
3 | 2023-06-01 21:53:33.822202 | Daniela | Tamar | 10 | |
4 | 2023-06-01 21:53:33.822202 | Daniela | Banco Central | 20 | |
5 | 2023-06-01 21:53:33.838537 | Banco Central | Jordy | 90 |
Finalmente, se verifica la integridad de la cadena de bloques y se comprueba que la cadena está bien, porque todos los bloques siguen enlazados correctamente: la información encriptada de cada bloque corresponde con la clave previa guardada por el siguiente bloque.
COCOCOIN.verificar_integridad()
Verificando bloque4 535E CE98 8FB0 07AC 7285 EAEA 7FC7 3602 3B0B 7B59 9A06 DB34 7F70 5913 5039 D03E 535E CE98 8FB0 07AC 7285 EAEA 7FC7 3602 3B0B 7B59 9A06 DB34 7F70 5913 5039 D03E Verificando bloque3 75F3 7882 FD3F 557B 794B 888F 329F DAD9 222B B602 74D5 6293 09C9 8BC2 5AE0 D834 75F3 7882 FD3F 557B 794B 888F 329F DAD9 222B B602 74D5 6293 09C9 8BC2 5AE0 D834 Verificando bloque2 CC12 2115 4C6C 2442 9DA5 A251 92F3 66FF 04B7 23BE 1F67 70A7 B636 EE2C 7311 A18A CC12 2115 4C6C 2442 9DA5 A251 92F3 66FF 04B7 23BE 1F67 70A7 B636 EE2C 7311 A18A Verificando bloque1 F559 3EC7 AC2E 3B0D 8E3C 93B9 2277 A436 2F99 4EB6 C2F6 0C9E E1DD 1078 0545 384C F559 3EC7 AC2E 3B0D 8E3C 93B9 2277 A436 2F99 4EB6 C2F6 0C9E E1DD 1078 0545 384C Verificando bloque0 2392 3C50 00C8 A67A 9E97 E205 C297 447B 0CBA 24E3 DF48 0136 1D5B DACB D1D9 4E60 2392 3C50 00C8 A67A 9E97 E205 C297 447B 0CBA 24E3 DF48 0136 1D5B DACB D1D9 4E60 LA CADENA DE BLOQUES ESTÁ BIEN!
Suponga ahora que un hacker logra infiltrarse en la cadena y modifica un dato, poniendo un saldo de 1000 cococoins a su favor:
COCOCOIN[3].saldos['HACKER'] = 1000
COCOCOIN[3].saldos
Banco Central 2770 Tamar 330 Esteban 700 Jordy 470 Daniela 730 HACKER 1000 dtype: int64
Al verificar la integridad de la cadena, se detecta que ha sido adulterada porque la modificación del 'HACKER'
ocasiona que el hash del bloque 3 ya no coincida con lo que el bloque 4 esperaba encontrar.
COCOCOIN.verificar_integridad()
Verificando bloque4 535E CE98 8FB0 07AC 7285 EAEA 7FC7 3602 3B0B 7B59 9A06 DB34 7F70 5913 5039 D03E 535E CE98 8FB0 07AC 7285 EAEA 7FC7 3602 3B0B 7B59 9A06 DB34 7F70 5913 5039 D03E Verificando bloque3 75F3 7882 FD3F 557B 794B 888F 329F DAD9 222B B602 74D5 6293 09C9 8BC2 5AE0 D834 8862 1797 81BE 1197 3F0F A435 8293 CAFA 0262 D8A5 9230 D9AA C44D B4ED 241C 0837 ADVERTENCIA: LA CADENA DE BLOQUES FUE ADULTERADA EN EL BLOQUE 3!!!
La cadena de bloques emite nuevos cococoins de manera sencilla: el 'Banco Central'
los emite y los pone en circulación “pagándole” a 'Tamar', 'Esteban', 'Jordy', 'Daniela'
600 cocoins a cada uno. En esta situación el 'Banco Central'
recibe todo el señoreaje.
Supongamos ahora que el 'Banco Central'
decide “democratizar” el señoreaje e implementa un sistema “PoW” (proof-of-work o prueba de trabajo), que consiste en acreditarle 20 cococoins al primero de sus cuatro colaboradores que resuelva un problema. Un problema PoW resulta relativamente lento de resolver pero sumamente fácil de verificar que ha sido resuelto.
La prueba de trabajo se implementa en los siguientes dos métodos:
nuevo_desafio
: el 'Banco Central'
escogerá de manera aleatoria un número entre 0 y $10^p-1$, lo encriptará usando la función encriptar
, y anunciará el código encriptado y el valor de $p$, donde $p$ es un entero que controla el nivel de dificultad del problema.
verificar_solucion
: al primero de los colaboradores que “adivine” el número aleatorio que dio origen al código anunciado, el 'Banco Central'
le acreditará 20 cococoins. Una vez hecho esto, se creará un nuevo bloque en la cadena de cococoin y se creará un nuevo problema.
Simulando ahora un primer desafío:
p = 4
COCOCOIN.nuevo_desafio(p)
Nuevo desafío (dificultad = 4): F2A6 D7D7 2458 7A4B 8A77 93EC FF5A BC0A F0B9 54AC CC0C 8DB3 ACF1 6E5B 3E57 A835
'Tamar', 'Esteban', 'Jordy', 'Daniela'
saben que es prácticamente imposible “invertir” el hash. Sin embargo, dado que conocen las reglas del juego, saben que ese código corresponde a un número entre 0 y 9999. Así, la alternativa que tienen es encriptar secuencialmente todos esos números y comparando los resultados con el código publicado.
En vez de ir encriptando uno a uno los números, escriben una función que hace todo el trabajo:
def adivinar(p, nombre):
for numero in range(10**p):
codigo = encriptar(str(numero))
if codigo == COCOCOIN.desafio:
print(f'{nombre} adivinó {numero}')
COCOCOIN.verificar_solucion(numero, nombre)
break
Esta función itera sobre todos los posibles números, los encripta y los compara con el hash. Una vez que resuelve el problema, le comunica a la cadena de COCOCOIN el resultado y el nombre del minero para que se le acrediten los fondos respectivos.
Cuando 'Tamar'
empieza a minar, logra encontrar el número que el 'Banco Central'
había escogido, y consigue así 20 cococoins de recompensa.
adivinar(p, 'Tamar')
Tamar adivinó 3614 Banco Central le pagó 20.00 cococoins a Tamar. Nuevo desafío (dificultad = 4): AC18 C76E 7153 5FAC 7799 88A8 8A99 D12D A711 F418 6BB1 0312 B9D8 FDAF 5E24 A695
'Esteban', 'Jordy' y 'Daniela'
también están interesados en ganar estos cococoins con solo tener sus propias computadoras ejecutando la función adivinar
. Esta vez 'Esteban'
obtiene los siguiente 20 cococoins:
adivinar(p, 'Esteban')
Esteban adivinó 7894 Banco Central le pagó 20.00 cococoins a Esteban. Nuevo desafío (dificultad = 4): 7AC2 0212 A76D 72DC B5C2 9CC4 51E9 2228 C1FA 6CE0 8279 3CED 2E81 BF0F 3823 C449
Conforme aumente el valor de los cococoins, el número de interesados en minarlos irá aumentando. Dado que solo el primer minero en resolver el problema actual recibirá cococoins, compiten aumentando la capacidad computacional. Con tantos mineros y recursos dedicados a esta actividad, la velocidad con la que se resuelven los problemas aumenta y la cadena eventualmente responde aumentando el valor de $p$, para que sea cada vez más difícil resolverlos (de lo contrario se crean bloques nuevos demasiado pronto).
El resultado final de todo esto es el dilema del prisionero.
En esta situación hay dos equilibrios: el primero de ellos es un equilibrio inestable, donde todos los mineros dedican un mínimo de recursos computacionales a resolver los problemas, y el segundo es un equilibrio estable en el cual todos los mineros dedican muchísimos recursos.
El dilema se presenta porque, aunque en ambos equilibrios el premio es el mismo, el costo de minarlos en el segundo equilibrio es considerablemente mayor al del primero.
A pesar de ser socialmente deseable el primer equilibrio, ¿por qué es inestable?: porque en tal situación para cada minero es individualmente deseable dedicar más recursos a la minería, para aumentar sus propios ingresos esperados. Como todos los mineros saben esto, todos terminan dedicando más recursos a la actividad.
Esta manera de “democratizar” el señoreaje a través de la minería no es más que un gran desperdicio de energía. De hecho, el consumo energético de la red de Bitcoin es tan alto que alcanzaría para dotar permanente de electricidad a un país entero (Austria).
Muchas veces no es necesario saber cómo funciona algo para sacarle provecho: no se necesita saber cómo funciona un motor de combustión interna para manejar un carro. Pero para juzgar si un sistema de pagos basado en una innovación informática es seguro y confiable, sí resulta necesario entender más.
Conociendo cómo funciona el blockchain, hay, al menos, dos reflexiones:
(1) ¿puede un sistema de pagos prescindir de la confianza?, y (2) ¿es realmente el blockchain una tecnología valiosa?
Según su creador, Bitcoin fue pensado como un sistema de pagos completamente descentralizado, para sustituir lo que consideraba un sistema ineficiente
La raíz del problema con el dinero convencional es toda la confianza que se requiere para hacerlo funcionar. Debe confiarse en que el banco central no degrade el dinero, pero la historia del dinero fiduciario está lleno de incumplimientos a esta confianza. Debe confiarse en que los bancos guarden nuestro dinero y que lo transfieran de manera electrónica, pero los bancos lo prestan en olas de burbujas de crédito con apenas una fracción en reserva.
-- Nakamoto (2009), traducción libre.
Lo que parece ignorar Nakamoto es que todo sistema de pagos necesita de que sus usuarios confíen en su correcto funcionamiento. Su afirmación de que su Bitcoin es un sistema de pagos completamente descentralizado "porque todo se basa en prueba criptográfica en vez de confianza” resulta cínica.
¿Por qué? Porque lo único que hace bitcoin es obligar a sus usuarios a trasladar la confianza en las instituciones financieras a la red de pagos de Bitcoin.
Considere los siguientes casos ilustrativos:
1. al ser Bitcoin un sistema basado en tokens o fichas quedebo poseer para demostrar que son mías (como sucede con el dinero en efectivo), al resguarlas debo confiar en:
2. al utilizar un monedero en Internet para evitar los riesgos del punto 1, hay que confiar en el dueño de tal sitio web, del que posiblemente ni siquiera sé dónde está domiciliado. En este caso debo confiar tanto de su honorabilidad como de su competencia (no son infrecuentes las noticias de ciberataques a este tipo de sitios). Esto es similar a la confianza requerida para depositar efectivo en un banco.
3. para gastar los bitcoins, es necesario confiar en los proveedores de conexión al blockchain de Bitcoin (sin esta conexión resulta literalmente imposible pagar con bitcoins). La inmensa mayoría del público carece de las competencias informáticas necesarias para conectarse directamente a Bitcoin, por lo que al final terminan confiando en proveedores de aplicaciones.
4. según lo prometido por Nakamoto, el Bitcoin es una red “completamente descentralizada”, lo cual resultaría atractivo en tanto esto haría que el valor de Bitcoin no dependiese de las decisiones arbitrarias de un pequeño grupo de personas. Pero la descentralización de la red no implica la descentralización de los saldos de bitcoins, por lo que algunos pocos participantes en este mercado pueden manipular fácilmente su precio (Kharif 2017, Orcutt 2018).
5. si se quiere el Bitcoin como resguardo de valor, hay confiar en que su precio será estable en el tiempo. En esto es donde peores resultados ha dado la criptomoneda, dado su enorme volatilidad, ante la cual las pérdidas de valor de la moneda fiduciaria mencionadas por Nakamoto apenas parecen visibles.
En este último punto en específico es importante señalar que luego de la crisis financiera internacional de 2008 el dinero fiduciario de las principales economías del mundo no perdieron mucho valor (las tasas de inflación han permanecido realmente bajas en la última década). Lo que sí ocurrió fue un aumento en la variabilidad de los tipos de cambio, pero hay que recordar que un tipo de cambio es el precio relativo de una moneda en términos de otra, por lo que si una baja su valor entonces necesariamente la otra lo sube.