19 julio 2021

Escribiendo Mensajes al Blockchain de Bitcoin

Excepción de responsabilidades (Y opinión editorial)

Los que me conocen saben que en general considero las tecnologías blockchain y criptomonedas como tecnologías altamente experimentales. Muchas soluciones en el "ecosistema" son normalmente implementaciones en búsqueda de problemas. Lastimosamente, muchas de estas "soluciones" no son la forma más eficiente o adecuada de arreglar los problemas que pretenden solucionar. El entorno está plagado de esquemas para "hacer dinero rápido". A sus proponentes usualmente no les interesa mucho la tecnología subyacente sino más bien posicionarse a si mismos como los nuevos ricos en el sistema monetario alternativo que proponen. Prometen demasiado y en la práctica terminan entregando muy poco... Dejándonos con "soluciones" extremadamente complejas para el usuario final y con muy poco valor agregado. Esta cultura se puede atribuir a que muchos involucrados en el ecosistema son incapaces de separar la parte tecnológica de la parte utópica/ideológica y eso hace muy difícil el hacer evaluaciones objetivas de la tecnología y determinar su aplicabilidad real en distintos casos de uso.

Habiendo dicho lo anterior: Las instrucciones contenidas en esta entrada de blog terminan inevitablemente en la pérdida de criptomoneda (bitcoin) ya que para escribir mensajes en el blockchain debemos de pagar un cargo mínimo a los mineros para que verifiquen la transacción. Por lo tanto: no me hago en absoluto responsable de que pierdan su bitcoin al seguir estas instrucciones. Estan advertidos.

Comencemos por lo básico: ¿Qué es el Blockchain de Bitcoin y el problema del doble gasto?

El bitcoin es la primera implementación exitosa de un mecanismo electrónico para el registro de intercambio de valor que no requiere de un ente central para verificar la validez de las transacciones que reflejan estos intercambios.

Satoshi Nakamoto (El inventor de Bitcoin) ideó un mecanismo para evitar que un actor malicioso engañe a los participantes del sistema y reporte un "doble gasto".

El problema del doble gasto puede explicarse de la siguiente manera: Imaginemos por ejemplo que Alice quiere enviar a Bob 5 unidades de valor a través de un medio electrónico. En un sistema descentralizado tienes el problema que no existe un ente de confianza que garantice que esa transacción es única y que Alice no ha enviado esas 5 unidades a alguien más.

La forma en que Satoshi resuelve ese problema es combinando dos mecanismos:

  1. Un registro público distribuido al cual solo se pueden agregar transacciones nuevas conocido como cadena de bloques (Blockchain en inglés).
  2. Un mecanismo distribuido de consenso que desincentiva que un usuario mal-intencionado registre múltiples veces una transferencia de valor.

El nombre de "cadena de bloques" o "blockchain" como le llamaremos en esta entrada, se deriva del hecho que las transacciones no son agregadas una a una al final del blockchain sino más bien son agrupadas en bloques de transacciones utilizando una estructura de datos conocida como "Árbol de Merkle".

El mecanismo de consenso

A pesar de que el registro distribuido haría evidente las intenciones de un usuario malicioso de ingresar transacciones fraudulentas, aún podría convencer a muchos nodos de validar su transacción en lo que es conocido como el ataque de sybil.

Para prevenir esto: Los participantes que validan transacciones deben de proveer el resultado de una prueba que requiere utilizar recursos computacionales para resolverse en lo que conocemos como "prueba de trabajo". Este proceso es conocido popularmente como minado y a los usuarios que lo realizan: mineros.

Al agregar al sistema la necesidad de realizar una prueba de trabajo para validar las transacciones un atacante no solo tiene que transferir el resultado de la validación sino demostrar que ha completado la tarea de forma satisfactoria para que su respuesta sea aceptada por la red.

El sistema fue diseñado de tal manera que ajusta automáticamente la dificultad de la prueba para que un actor malicioso de forma individual no pueda superar a la capacidad de computo que se agrega de forma colectiva de todos los que participan de validar las transacciones.

Aún así, es posible que un atacante con recursos suficientes esté en capacidad de superar de forma individual la capacidad de procesamiento de la mayoría de nodos de la red en lo que es conocido como el ataque del 51%. En la historia de la tecnología de bitcoin el grupo de minadores (Mining pool) gHash.IO superó brevemente la barrera del 50% pero se comprometieron a reducir sus capacidades de computo luego de recibir críticas de la comunidad de Bitcoin.

Ahora hablemos de las transacciones

Las transacciones en bitcoin son registradas de una manera un tanto diferente a como registrarías una transacción en una base de datos o una tabla de excel. Normalmente en una base de datos centralizada podrías tener una tabla que contiene el saldo correspondiente a cada usuario del sistema.

El problema es que si intentas replicar ese esquema dando una copia de la tabla a todos los participantes. ¿Cómo te aseguras que todos tengan los mismos saldos considerando que dos transacciones podrían ocurrir en paralelo?

La forma en que esto se resuelve en bitcoin es dejar de lado por completo un registro centralizado y adoptar un estructura de datos denominada Unspent transaction(TX) Output (O) (UTXO).

Las UTXO son estructuras de datos que representan los "remanentes"(Vueltos en lenguaje coloquial) de transacciones anteriores. Un usuario solo puede generar una nueva transacción a partir de los remanentes de transacciones previas.

El saldo atribuido a una dirección en bitcoin corresponde a las "salidas" sin gastar de UTXO previas.

Para explicar el funcionamiento de las UTXO vamos a simplificar su funcionamiento con un ejemplo de transacciones entre varios usuarios. 

En el siguiente diagrama puedes ver como Alice tiene 10 bitcoins que se le atribuyeron a ella en una de las salidas de la UTXO creada por Joe:

Nota como las UTXO deben cumplir con la fórmula:

(Entradas) = (Salidas+Cambio)

Si Alice quiere enviar 5 bitcoins a Bob. Debe de crear una nueva UTXO donde especifique en las "entradas" los remanentes de las transacciones que le han sido atribuidas no gastadas y en las salidas las direcciones a donde desea enviar bitcoin y sus cantidades como se muestra en el siguiente diagrama:

En el diagrama podemos ver como Alice utiliza el remanente sin utilizar de la transacción creada por Joe y lo especifica como entrada en la nueva transacción donde envía 5BTC a Bob. Nota también como es necesario que Alice se atribuya a si misma la cantidad que desea mantener "sin gastar". Esto es necesario ya que toda cantidad que no se especifique en la salida se considera como parte del "cambio" y será entregada al minero.

Para cada transacción creada hay un pequeño "cambio". El cambio es lo que se paga al minero por procesar la transacción. En este ejemplo estamos usando 0.01 BTC solo como ejemplo. Pero usualmente los mineros no cobran tarifas tan altas. Actualmente la tarifa es inferior a los 0.00001000BTC (1000 satoshis) en la mayoría de transacciones.

Continuando con ejemplo imaginemos que ahora Alice recibe 7BTC adicionales de Guy. Asumiendo que Alice no gasta su saldo aún, la nueva transacción se vería de la siguiente manera:

Según el diagrama anterior podemos ver que Alice ahora cuenta con dos saldos sin gastar. El primero que se atribuyó a si misma por 4.99BTC en la transacción donde envio 5BTC a Bob y los 7BTC que Guy le atribuyó a ella.

Con estos dos saldos sin "gastar" ahora Alice puede enviar 11 BTC a Mike creando una nueva UTXO utilizando estos saldos remanentes.

Asumiendo que no se realizan más transacciones. Al finalizar este ejemplo Joe tiene una salida sin gastar por 1.99BTC, Alice tiene una salida sin gastar por 0.98BTC y Guy tiene una salida sin gastar por 2.99BTC.

En UTXO reales en lugar de utilizar un nombre de usuario utilizamos la dirección de destino que nos proporciona la persona a quién queremos enviar el bitcoin y cada transacción es identificada por un hash (Un número derivado del mismo contenido de la transacción) que sirve como identificador único y para poder referenciar los saldos sin gastar en nuevas transacciones.

Aunque este mecanismo parece bastante complejo. Permite la atribución de saldos a terceros de forma sencilla sin el problema de tener que mantener un registro centralizado o determinar que operación se realiza primero sobre el saldo de un usuario particulaar.

Un pequeño detalle más...

Las UTXO resuelven el problema del registro. ¿Pero como conoce la red que es Alice quien autoriza la transacción y no alguien más?

Aquí es donde entran en juego las firmas digitales: Cada UTXO debe de ser firmada digitalmente con la llave privada del dueño de las direcciones que referencian las UTXO sin gastar. Esencialmente la UTXO es un mensaje digital que dice "Yo como dueño de este saldo sin gastar doy fe de que el dueño de esta otra billetera puede hacer uso de este saldo remanente".

Las UTXO permiten lógica más compleja

Una de las cosas más interesantes acerca de esta tecnología es que es posible especificar reglas mucho más complejas para las transacciones. Bitcoin posee una serie de instrucciones "de bajo nivel" que sirven para que los usuarios creen condiciones complejas que determinen cómo puede gastarse cada UTXO. Esto abre la puerta a lo que conocemos como contratos inteligentes (o Smart contracts).

En esta entrada no vamos a dedicar a profundizar en smart-contracts ya que bitcoin al ser la primera tecnología de su clase tiene capacidades limitadas al respecto. Sin embargo, otras tecnologías de blockchain más recientes como Ethereum permiten definir contratos inteligentes sumamente complejos.

Sin embargo, ya que lo utilizaremos más adelante en esta entrada debemos hacer mención de la operación de bajo nivel OP_RETURN. Esta operación permite que el UTXO incluya un "mensaje de retorno". Esta operación ha sido utilizada como un mecanismo para dejar mensajes o adjuntar meta-datos a las UTXO. Aunque no es una característica muy utilizada, algunos usuarios de bitcoin han ocupado esta funcionalidad para dejar mensajes secretos y hasta notas de amor dentro del blockchain de bitcoin.

Y llegamos a la parte del blog donde vamos a perder nuestro bitcoin...

Nuestro objetivo en esta sección es crear una UTXO que sea similar a la que se muestra en el siguiente diagrama y lograr que los mineros la registren en el blockchain:

Para este ejercicio asumiremos que somos Alice y tenemos cierta cantidad de Bitcoin disponible. Vamos a crear una UTXO donde una de las salidas sea la operación OP_RETURN junto a un mensaje arbitrario y la otra sea una atribución a nosotros mismos con el remanente - lo que deseamos pagar al minero para que procese nuestra transacción.

Ingredientes...

Otros tutoriales similares en Internet les recomendarán hacerlo todo desde el cliente Bitcoin Core. El problema es que para enviar las transacciones requerimos que el cliente esté actualizado con todo el blockchain y esto ocupa mucho espacio y tiempo. En lo personal, me parece que el cliente Electrum es más amigable para manejar los saldos y enviar las transacciones. Así que para este tutorial utilizaremos las dos herramientas.

Nota: En esta entrada no voy a explicar a detalle cómo agregar saldos o comprar bitcoin. Asumo que pueden realizar la transferencia a la dirección de la billetera Electrum.

Paso 1: Elegir un mensaje y convertirlo a una representación hexadecimal.

Podemos utilizar un servicio en línea como ASCII to HEX para convertir nuestros mensajes. Solo recuerda que existe una limitación de 80 caracteres máximo y limítate a utilizar caracteres ASCII en lugar de caracteres acentuados ya que estos no se muestran bien en muchos exploradores de blockchain.

Voy a guardar el mensaje:

"Hello World!"

En hexadecimal utilizando la herramienta de conversión en línea:

48656c6c6f20576f726c6421

Almacenamos estos datos para despues.

Paso 2: Obtener la información de las UTXO sin gastar

Para este paso necesitamos usar nuestra billetera Electrum.

Primero: Hacemos click en el menú "View->Show Console".

En la consola podemos obtener los saldos sin gastar con la instrucción listunspent():

Aquí debemos de tomar nota de la siguiente información:

  • address es la dirección que tiene el BTC que vamos a gastar (bc1qq0gz4m3w3zeazxkxmqk5rud8uzfj3dtp7ayhge)
  • prevout_hash es la UTXO que contiene nuestro BTC sin gastar (979d4613fbe53053640218214ac0f6afdd419fa01d103f3008c08a9f634f4622)
  • prevout_n es el correlativo de operación de salida en al UTXO que estamos referenciando (1)
  • value el valor actual de BTC almacenado en ese saldo sin gastar (0.0000867)

Luego necesitamos obtener los detalles de la transacción previa. Esto lo hacemos con el comando deserialize(gettransaction('<prevout_hash>')).


Aquí nos interesa el contenido que se encuentra dentro de "outputs", como el número prevout_n es 1 para este ejemplo, tomaremos la segunda entrada (Comenzamos a contar de 0) de la lista y copiaremos el código hexadecimal que aparece en "scriptpubkey" (001403d02aee2e88b3d11ac6d82d41f1a7e09328b561).

Paso 3: Generar nuestra transacción personalizada

En este paso utilizaremos el cliente Bitcoin Core. En esta aplicación nos vamos al menú y abrimos la opción "Window->Console".

En la consola podemos verificar la ayuda del comando createrawtransaction. Que muestra una ayuda como la siguiente:

 

createrawtransaction [{"txid":"hex","vout":n,"sequence":n},...] [{"address":amount},{"data":"hex"},...] ( locktime replaceable )

 

El comando nos permite crear una nueva transacción donde especificamos primero todas las "Entradas" que vamos a gastar y luego las "Salidas". Una de estas salidas puede ser el comodín "data" que indica que usaremos la instrucción de bajo nivel OP_RETURN.

 

Podemos crear una nueva transacción "cruda" llenando con la información que colocamos en el paso anterior de la siguiente manera:


createrawtransaction '[{"txid":"<prevout_hash>","vout":<prevout_n>}]' '[{"data":"<HEX_message>"},{"<address>":<cantidad>}]'


Ten cuidado en esta sección que "cantidad" corresponde a value-(comisión de transacción). Para este ejemplo en "value" tenemos 8670 sats, el valor que ingresemos en cantidad debe de ser al menos 200 sats. Por lo que deberemos de ingresar un total de 8470 sats en este ejemplo.

 

Nuestro comando quedaría de la siguiente manera:

 

createrawtransaction '[{"txid":"979d4613fbe53053640218214ac0f6afdd419fa01d103f3008c08a9f634f4622","vout":1}]' '[{"data":"48656c6c6f20576f726c6421"},{"bc1qq0gz4m3w3zeazxkxmqk5rud8uzfj3dtp7ayhge":0.00008470}]'


Hasta aquí tenemos la mitad del trabajo hecho. Copiamos el código hexadecimal que genera la consola de Bitcoin Core porque necesitamos firmar esta transacción.

 


El código hexadecimal de la operación anterior:

020000000122464f639f8ac008303f101da09f41ddaff6c04a211802645330e5fb13469d970100000000ffffffff0200000000000000000e6a0c48656c6c6f20576f726c6421162100000000000016001403d02aee2e88b3d11ac6d82d41f1a7e09328b56100000000


Lo siguiente es firmar digitalmente la transacción para indicar a la red que nosotros estamos en control de los saldos remanentes que serán utilizados en la entrada.

 

Para firmar la transacción necesitamos obtener la llave privada de la dirección que utilizaremos para publicar el mensaje. Para hacer esto regresamos a Electrum y hacemos click derecho en la dirección a utilizar y hacemos click en Private Key.


Electrum te pedirá ingresar tu clave y luego te mostrará la clave privada. Asegúrate de no guardar esta clave en ningún archivo en disco. Debido a que no es seguro poner una llave privada en el blog he censurado la llave privada. 

Puedes usar un editor simple de texto para copiarla, pero *repito* no guardes esta clave en almacenamiento permanente.

Para firmar nuestra transacción necesitamos ejecutar el comando signrawtransactionwithkey de la siguiente manera:

signrawtransactionwithkey "--codigo HEX del comando createrawtransaction--" '["llave privada copiada de Electrum"]' '[{"txid":"<prevout_hash>","vout":<prevout_n>,"scriptPubKey":"<scriptpubkey>","redeemScript":"","amount":<value>}]'

En nuestro caso el ejemplo quedaría así:

signrawtransactionwithkey "020000000122464f639f8ac008303f101da09f41ddaff6c04a211802645330e5fb13469d970100000000ffffffff0200000000000000000e6a0c48656c6c6f20576f726c6421162100000000000016001403d02aee2e88b3d11ac6d82d41f1a7e09328b56100000000" '["--Llave privada copiada de Electrum--"]' '[{"txid":"979d4613fbe53053640218214ac0f6afdd419fa01d103f3008c08a9f634f4622","vout":1,"scriptPubKey":"001403d02aee2e88b3d11ac6d82d41f1a7e09328b561","redeemScript":"","amount":0.0000867}]'


Si ingresamos todos los datos correctamente, la operación nos generará otra cadena exadecimal que corresponde a nuestro mensaje firmado.

 


Código hexadecimal resultado de lo anterior: 

0200000000010122464f639f8ac008303f101da09f41ddaff6c04a211802645330e5fb13469d970100000000ffffffff0200000000000000000e6a0c48656c6c6f20576f726c6421592100000000000016001403d02aee2e88b3d11ac6d82d41f1a7e09328b56102473044022053f4bad978382c689fc5c1b8dd47cecae6ecf2f9232dc87d8869f3b5da8da38602207a4e0d566d0ceaece283c7980344fd90796a46d556dee8a948013804a0a76beb012103020446f8898b47ac7dc14b7fe41ce07957ba5d7bb7b9687adc6a1b52a69c481200000000

 

El siguiente paso es copiar el código hexadecimal del comando anterior y pegarlo en Electrum. Para ello nos vamos a Tools->Load Transaction->From Text:

Una vez cargada podemos explorar la transacción donde nos muestra el cálculo de la comisión y el tamaño de la transacción. En verde nos aparecen las direcciones que nosotros controlamos. Aquí puedes verificar que la transacción sale de nuestra billetera y regresa a nuestra misma billetera:


Para este ejemplo nuestro mensaje firmado tiene un tamaño de 133 bytes pero colocamos 200 sats de comisión. Así que nuestra comisión es de 1.5 sats/vByte. Lo mínimo que podríamos establecer para enviar este mensaje son 133 sats.

Luego de hacer click al botón broadcast... Si hemos seguido las instrucciones, nuestra transacción será aceptada y validada por los mineros. Podemos copiar el ID de la transacción para verificar si nuestro mensaje fue procesado.

 

 

ID de transacción de este ejercicio:

 

0bf1a0e86dc82d2c31da281acc6eca0c95021c9cad9051a4a57ed383ee3f5ddb

 

Podemos verificar nuestro mensaje en el sitio bitcoinsays.com ingresando la transacción:



O podemos utilizar un explorador del blockchain como mempool.space


 

Reduciendo los recargos

Usualmente para este tipo de tareas no nos interesa que la transacción sea confirmada de inmediato, así que podemos optimizar la comisión fácilmente.


Antes de apretar "Broadcast" revisamos el tamaño del mensaje en Bytes. Para ello debemos de repetir el proceso nuevamente desde el paso 3 en adelante. La comisión mínima es 1sat/vByte. Es decir si aparece que el mensaje *firmado* pesa 133bytes entonces calculamos un valor de comisión correspondiente al total no gastado - 133.

 

En este caso: 8670 - 133 = 8537

 

Volvemos a correr los comandos en Bitcoin Core:

 

createrawtransaction '[{"txid":"979d4613fbe53053640218214ac0f6afdd419fa01d103f3008c08a9f634f4622","vout":1}]' '[{"data":"48656c6c6f20576f726c6421"},{"bc1qq0gz4m3w3zeazxkxmqk5rud8uzfj3dtp7ayhge":0.00008537}]'

 

Copiamos el HEX y lo pegamos en:

 

signrawtransactionwithkey "020000000122464f639f8ac008303f101da09f41ddaff6c04a211802645330e5fb13469d970100000000ffffffff0200000000000000000e6a0c48656c6c6f20576f726c6421592100000000000016001403d02aee2e88b3d11ac6d82d41f1a7e09328b56100000000" '["--Llave privada copiada de Electrum--"]' '[{"txid":"979d4613fbe53053640218214ac0f6afdd419fa01d103f3008c08a9f634f4622","vout":1,"scriptPubKey":"001403d02aee2e88b3d11ac6d82d41f1a7e09328b561","redeemScript":"","amount":0.0000867}]'


Y copiamos el código hexadecimal de la transacción a Electrum donde podemos verificar que la nueva tarifa es 1sat/byte:



Solo toma en consideración que si envías transacciones de 1sat/vByte posbilemente estas puedan tardar algunas horas en confirmarse si la red está demasiado saturada. Esto tiene el efecto indeseado de que no podrás enviar otras transacciones con los saldos remanentes hasta que esta transacción sea confirmada.


Otras aplicaciones interesantes


Una aplicación interesante de estos mensajes es un procedimiento conocido como "Prueba de existencia". Una prueba de existencia es un mecanismo electrónico seguro que garantiza que un documento existía desde al menos el momento en que se verificó su existencia.


Podríamos usar un hash criptográficamente seguro para generar una "huella digital" de un documento o conjunto de documentos y publicar el hash en una transacción de bitcoin.

 

Esto sirve como prueba de que el documento existía al momento que se registro en el blockchain y si sus contenidos se cambian de alguna forma la huella digital no será la misma por lo que también sirve como evidencia de que los contenidos no han sido modificados desde que se demostró su existencia.


Si los documentos son firmados digitalmente con otros mecanismos de firma digital este mecanismo también podría ser utilizado para hacer "timestamping" o dar fe que un documento se firmó en una fecha determinada.


Algunos sitios web ya ofrecen estos servicios en línea pero el costo es mucho mayor al costo de hacerlo "a mano" como se explica acá.

 

Al precio que se cotiza bitcoin al momento de escribir esta entrada cada mensaje en el blockchain tiene un costo de unos $0.05USD/Mensaje.


Y esto es todo por ahora. Espero que les haya gustado.


¡Hasta la próxima!

No hay comentarios: