Realizando doble gasto 0-conf en Bitcoin SV

Siempre me pregunté si sería posible realizar un doble gasto de una transacción 0-conf en Bitcoin. Aunque todos los aspectos técnicos indicaban que sí sería posible, siempre que consultaba a algún experto obtenía las mismas respuesta:

  • La propagación de las transacciones en Bitcoin es muy rápida, es casi imposible hacer doble gasto.
  • Los mineros siguen la regla de «first seen safe»: una vez que aceptan una transacción no aceptarán otra que intente hacer un doble gasto de la primera.
  • Sobornar a un minero para hacer doble gasto de una transacción 0-conf no es rentable para cantidades pequeñas.
  • Siempre que se usen comisiones >= 1 satoshi/byte es seguro.
  • Aunque alguien consiga hacer un doble gasto de una 0-conf, detectarlo es trivial conectándote a varios nodos y esperando 5 segundos.
  • No hay forma de hacer un doble gasto sin ser detectado.

Como esas respuestas no me convencían, decidí hacer por mi cuenta una pequeña investigación. Como suele decirse: no confíes, verifica.

Bitcoin SV

He decidido realizar el estudio basado en Bitcoin SV (BSV), por varias razones. La principal es porque su comunidad cree firmemente que las transacciones 0-conf son lo suficientemente seguras tal como están, y que no necesitan ser mejoradas. Por ejemplo, Craig Wright opina lo siguiente sobre este asunto:

¿Es cierto lo que dice CSW?, ¿las probabilidades de detección de un doble gasto 0-conf son del 99.8%, a no ser que haya una confabulación con un minero deshonesto? Veamos…

Race condition

Lo primero que intenté comprobar fue si era posible enviar una transacción a un nodo y otra transacción diferente (con el mismo input) a otro nodo. Es decir, un doble gasto. La respuesta es clara: sí, es posible siempre que se haga de forma precisa. Para estas pruebas me basé en izubitcoin, una pequeña utilidad que programé hace tiempo para practicar C++. Trabajé sobre una nueva versión aún no publicada (v0.16), con las funcionalidades necesarias para realizar doble gasto. 

Screenshot de opciones izubitcoin v0.16

Explotando la race condition

Para conseguir explotar la race condition de aceptación de transacciones de doble gasto, lo que hace izubitcoin es lo siguiente:

  • Utiliza paralelización. Crea los procesos y threads necesarios para poder manejar la conexión a cada nodo de forma independiente. De este forma, y combinado con un servidor dedicado con una conexión de capacidad media, es capaz de conectarse a miles de nodos de forma simultánea en unos pocos segundos (toda la red de BSV o BCH).
  • Se conecta usando el protocolo de Bitcoin de forma nativa. Concretamente lo que hace es conectar al nodo remoto, intercambiar version/verack, ping/pong y se queda a la espera.
  • Una vez conectado a los nodos remotos (después unos pocos segundos) envío una signal a los procesos. Al recibirla cada thread envía la transacción a su nodo y se desconecta. De esta forma la sincronización es muy efectiva.

Los resultados son positivos en un altísimo porcentaje, variando ligeramente dependiendo del número de nodos a los que se conecte. Para la red BSV (unos 450 nodos) se consigue explotar la race condition con un porcentaje de éxito que varía entre el 90% y el 97%. De esta forma se obtiene un control muy preciso sobre qué transacción se envía a cada nodo.

Evitando que los nodos propaguen la transacción

Una vez que se consigue explotar la race condition de forma controlada la cosa se simplifica. Para evitar que los nodos propaguen la transacción entre ellos y conseguir así un control preciso sobre la distribución de nuestras 2 transacciones, el método es simple. Debemos conectarnos a todos los nodos de la red y enviarles las transacciones simultáneamente. Esto parece complicado, pero no lo es. Utilizando paralelización y una conexión de capacidad media se consigue de forma sencilla. Utilizando izubitcoin lo he realizado muchas veces con la red de BCH (unos 2000 nodos), y otras tantas con la red de BSV.

Utilizando el envío simultáneo puedes enviar la transacción T1 a 300 nodos, y la transacción T2 a 150 nodos. No importa si T1 y T2 comparten algún input (doble gasto). Cada nodo después de recibir su transacción intentará reenviarla, y entonces pueden pasar 2 cosas (excluyentes entre sí):

  1. El nodo destino ya tiene la misma transacción.
  2. El nodo destino tiene la otra transacción (doble gasto), por tanto no la acepta. La rechaza silenciosamente.

Mineros

Llegados a este punto, el objetivo estaba claro. ¿Y si enviamos la transacción T1 a la mayoría de nodos no mineros, y a la minoría de nodos mineros le enviamos la transacción de doble gasto T2? Con esto se conseguiría que la mayoría de la red viese únicamente la transacción T1, mientras que por otro lado la transacción que se minaría en el siguiente bloque sería la T2, puesto que es la única que tendrían los mineros. Debería funcionar, pero… ¿qué nodos son los mineros?

Identificando a los nodos mineros, ¿cuáles son?

Había que identificar los nodos mineros, ¿pero cómo?…

Mi primera idea fue conectarme a todos los nodos de la red de forma permanente, y registrar cada vez que un nodo anunciaba un bloque junto con una marca de tiempo con precisión de microsegundos. Dejándolo el tiempo suficiente (varios días), intentaría descubrir los nodos mineros a través del análisis estadísticos de esos datos. Creía que a medio plazo los nodos mineros anunciarían los bloques mínimamente antes que el resto, y que sería suficiente para identificarlos. Pronto descubrí que, aunque tenía cierto éxito, no era suficiente y el porcentaje de fallos a la hora de hacer doble gasto era demasiado elevado.

Luego se me ocurrió una idea. ¿Y si enviamos a cada nodo una transacción única? En vez de un doble gasto sería una especie de cuatrocientos cincuenta gasto, uno por cada nodo de la red BSV. Modifiqué izubitcoin para que enviase a cada nodo una transacción que utilizase el mismo input, pero añadiendo un output con un OP_RETURN + datos aleatorios. Guardando un registro de a qué nodo se le envió cada transacción (txid), luego podría ver qué transacción de las 450 es la que se minaba en el siguiente bloque. De esa forma me serviría para identificar a los nodos mineros.

Efectivamente, a los pocos bloques descubrí que las transacciones que se minaban eran las que se enviaban casi siempre a los mismos nodos. También confirmé lo que ya sabíamos, que la minería de Bitcoin SV está muy centralizada. Concretamente:

  • El 34% del hashrate es únicamente 1 nodo.
  • El 59% del hashrate son 2 nodos.
  • El 68% del hashrate son 3 nodos.
  • El 75% del hashrate son 4 nodos.

Por tanto, si hay 450 nodos en la red BSV; se podría enviar la transacción T1 a 446 nodos, la transacción T2 a 4 nodos, y las probabilidades de que se minase la transacción T2 serían del 75%.

Pruebas de doble gasto 0-conf reales en BSV

He realizado muchos doble gasto en la red de Bitcoin SV. Desde que utilizo la técnica de enviar una transacción única a cada nodo para identificar a los mineros, las conclusiones son las siguientes:

  • Puedes elegir el porcentaje de éxito que desees. Para conseguir un 90% de éxito sólo tienes que enviar la transacción T2 a 6 nodos. Si quieres un 100% son 20 nodos.
  • Enviando la transacción T2 a 6 nodos, las probabilidades de detectar el doble gasto son muy pequeñas. He hecho una prueba con la aplicación POP! (de Handcash), que se conecta a unos 10 nodos para intentar detectar el doble gasto. Ha fallado y no ha detectado el doble gasto en ninguna de las ocasiones. Es lógico, teniendo en cuenta que si hay 450 nodos en la red BSV sus probabilidades de detección son del 14% apróximadamente.
  • Cuantos más nodos haya en la red, más difícil es detectar un doble gasto.
  • Todas las transacciones tienen una comisión superior a 1 satoshi/byte.
  • El tiempo de ejecución del doble gasto es inferior a 20 segundos.
  • Realizarlo es prácticamente gratis, sólo hace falta una conexión a internet y un router de gama media/alta.
  • Funciona aunque todos los mineros sean honestos.

¿Cómo se podría mitigar el problema?

En mi opinión la solución es simple. Los nodos deben comunicarse entre sí cuando reciban una transacción de doble gasto (enviando una prueba), ignorarla de forma silenciosa no tiene sentido. Creo que Bitcoin Unlimited ha propuesto (o ya ha desarrollado) algo al respecto. De esa forma se podría detectar el intento de doble gasto en el mismo instante en el que se realiza, sólo habría que esperar unos 5 segundos.

Otras soluciones como conectarse a un mayor número de nodos, o hacer pública la lista de nodos mineros; creo que sólo son parches poco eficientes que no solucionan el problema. No creo que lo necesario sea aumentar la complejidad de wallets y procesadores de pago para que intenten detectar el doble gasto de forma poco eficaz, sino modificar el protocolo para conseguir que detectar un intento de doble gasto sea algo tan sencillo que incluso una wallet SPV en un smartphone pueda hacerlo.

Pruebas en entornos reales

He realizado doble gasto en varios servicios de la red BSV, siempre utilizando cuentas y direcciones propias (no de terceros). Adjunto un vídeo demostrativo de un doble gasto en la aplicación POP! de Handcash. Es una aplicación PoS (point of sale) pensada para el comercio minorista, que incorpora detección de doble gasto.

3 comentarios en “Realizando doble gasto 0-conf en Bitcoin SV

Deja una respuesta

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