BLOG |
How-tos
Recuperación de 100 000 sats con copias de seguridad de la semilla y del estado.
Una salida unilateral real de la red Spark: las cifras, los fallos y las lecciones aprendidas. Todo lo que se describe a continuación tuvo lugar en la red principal de Bitcoin y puede verificarse públicamente en la cadena.
He llevado a cabo una salida forzosa de un monedero Blink sin custodia (utilizando Spark) en la red principal de Bitcoin. Todo lo necesario para la salida se derivó de la semilla del monedero: la financiación de las comisiones, la firma de la transacción y el vaciado final. El único elemento adicional es un paquete de recuperación, una instantánea de los datos de salida del monedero guardada mientras la red Spark aún era accesible. Con ese paquete en mi poder, la salida en sí no requirió la colaboración de nadie.
Las cifras reales para un monedero de 100 000 sat:
«Sin custodia» significa que siempre puedes marcharte. Pero la salida de emergencia es estrecha. Aquí tienes la historia completa, con los costes, los fracasos y las lecciones aprendidas.
«Sin custodia» es un término que se utiliza con facilidad en los textos de marketing. Queríamos que fuera algo que se pudiera verificar, no algo que se te pidiera creer. Así que cogimos un monedero Blink real, imaginamos que la red Spark había desaparecido y volvimos a introducir todos los sat recuperables en la cadena de bloques de Bitcoin utilizando únicamente la frase de semilla, un paquete de recuperación guardado y herramientas de código abierto que cualquiera puede ejecutar. Esta publicación es la prueba de ello.
Una cartera móvil de Spark que contiene 100 000 sats repartidos en 22 hojas.

Brevecontexto para los lectores que no conozcan Spark: los fondos de un monedero Spark residen en un árbol compartido en cadena gestionado por un conjunto de operadores, y cada hoja de ese árbol es una parte discreta del saldo del monedero con su propia ruta prefirmada de vuelta a Bitcoin. Una salida unilateral consiste en difundir esa ruta —la cadena de salida de la hoja, formada por transacciones en cadena que se confirman nivel por nivel a lo largo del árbol— para forzar la salida de la hoja hacia Bitcoin sin la cooperación de los operadores.
Un monedero Spark para móvil que contiene 100 000 sats repartidos en 22 hojas. Los monederos Spark acumulan hojas mediante su uso habitual (los pagos dividen y vuelven a dividir el árbol), y cada hoja lleva su propia cadena de salida: las transacciones en cadena que deben confirmarse, nivel por nivel, para forzar la salida de la hoja hacia Bitcoin sin la cooperación de los operadores. Para este monedero, eso supuso 253 paquetes de transacciones repartidos entre las 22 cadenas. Por 100 000 sats.
La recuperación solo a partir de la semilla no es posible una vez que los operadores de Spark están desconectados: las hojas actuales no se pueden determinar a partir de la semilla por sí sola. La salida requiere un paquete de recuperación —una instantánea en formato JSON de las hojas del monedero y sus transacciones ancestrales— que se actualice mientras los operadores aún estén conectados:
ejecuta «refresh-recovery-bundle» SEED_FILE=../.spark-seed.txt BUNDLE=../recovery-bundle.json
Primera lección práctica: nuestro primer paquete estaba incompleto sin que nos diéramos cuenta. El volumen de los operadores query_nodes(include_parents=true) La API omite el nodo raíz de los árboles de la red principal heredada, por lo que cada una de las 22 cadenas de salida presentaba un hueco en la parte superior, y la creación del paquete sin conexión falló con el mensaje La cadena de salida está incompleta. El problema se solucionó recuperando los antepasados que faltaban mediante el ID del nodo (lo que evita el salto de la raíz); ahora, el exportador lo hace automáticamente y rechaza escribir un paquete con cadenas abiertas.
Comprueba las cadenas de antecesores de tu paquete antes de que las necesites; un paquete incompleto que se detecte durante una interrupción del servicio es irrecuperable.
Las transacciones de salida se firman previamente sin comisión y pagan las comisiones a través de anclajes efímeros de CPFP, por lo que la salida necesita un UTXO independiente de Bitcoin en L1 para financiar los aumentos de comisión. La herramienta genera una dirección de financiación específica a partir de la propia semilla del monedero (ruta m/8797556'/<account>/0: solo el índice de propósito está protegido; los índices de cuenta y de dirección no lo están, por lo que un monedero de solo visualización puede deducir y supervisar la dirección de financiación a partir de un xpub sin necesidad de ninguna clave privada):
ejecuta cpfp-address SEED_FILE=../.spark-seed.txt BUNDLE=../recovery-bundle.json FEE_RATE=1
A 1 sat/vB, se necesitaban 78 573 sats para salir de las 22 hojas. Casi el 79 % del saldo del monedero en comisiones. Lo financiamos con 3ab20a4c…7262 antes de hacer los cálculos por hoja. Más información al respecto a continuación.
En el primer intento se empaquetaron, firmaron y enviaron los 253 paquetes de forma consecutiva a través del método POST /txs/package de Esplora. Se confirmó el primer paquete (padre 16895bc8…9619, hijo CPFP 384cdc6d…3b37). Los otros 252 fueron rechazados:
"error": "Infracción de TRUC: la transacción 16895bc8… superaría el límite de recuento de descendientes"
"error": "entradas de transacción incorrectas: faltan o ya se han gastado"
Las transacciones de salida de Spark son de tipo v3, también denominadas TRUC (Topologically Restricted Until Confirmation, BIP 431). La política del mempool limita un clúster v3 a un paquete principal sin confirmar más un paquete secundario. Eso es lo que hace que las transacciones de salida prefirmadas y sin comisión puedan aumentar su comisión de forma segura, y también significa que cada nivel de una cadena de salida debe confirmarse antes de que el siguiente nivel pueda entrar en el mempool. Una cadena de 15 paquetes tarda al menos 15 bloques, independientemente de cómo se envíe. Cualquier herramienta que envíe paquetes uno tras otro dejará varados todos los paquetes posteriores al primero de cada cadena.
Antes de automatizar el bucle, calculamos el precio de cada hoja: las comisiones de CPFP de su cadena más la retirada final de unos 111 vB, en comparación con el valor de la hoja. El resultado para esta cartera real a 1 sat/vB:
Cuatro hojas (32 768 + 32 768 + 16 384 + 8 192 sats) concentraban el 90 % del saldo. Las otras 18 —restos de la actividad habitual de la cartera, algunas de tan solo 1 sat— habrían costado cada una entre 2.100 y 4.600 sats para retirarlas: retirar todo habría supuesto gastar unos 77,5 mil sats en comisiones para recuperar 100 mil. La herramienta ahora calcula esto por cada «hoja» y omite por defecto las hojas no rentables (INCLUDE_UNECONOMICAL=1 anula esta configuración; los cálculos económicos se han sometido a pruebas de regresión con este mismo paquete, adaptado a regtest).
Tras cada sat de la cartera de 100 000 sat, a través de la salida económica a 1 sat/vB. Hay dos fondos en juego: el propio saldo de la cartera y los 9 388 sat de comisión que financian la salida económica y que se han consumido realmente de la dirección de financiación.
Los 100 000 sats de la cartera:
Las transacciones de salida y reembolso de Spark se firman previamente sin comisión alguna (las comisiones se basan en los valores de referencia del CPFP), por lo que cada salida de reembolso contiene el valor total de la hoja; la única deducción del saldo de la cartera es la comisión final de barrido.
Los 9.388 sats destinados a la financiación de las comisiones se han obtenido de:
En resumen: 89 668 de 100 000 sats (aprox. el 90 %) llegan a su destino. El coste total de la salida es de unos 18 700 sats —9 888 abandonados como «polvo», 8 388 en comisiones de CPFP y 444 en comisiones de barrido—, más la comisión en cadena de la transacción que financió la dirección de CPFP en primer lugar. A medida que aumentan las tarifas, todos los términos se incrementan y más «hojas» quedan por debajo del umbral económico; a 10 sat/vB, esta cartera abandonaría dos «hojas» más y pagaría diez veces más en comisiones por el resto.
El flujo reescrito consiste en un único comando:
ejecutar «recover» SEED_FILE=../.spark-seed.txt BUNDLE=../recovery-bundle.json NETWORK=mainnet FEE_RATE=1
En cada ronda, reconstruye los paquetes a partir del estado actual de la cadena, firma el aumento de CPFP con la semilla, envía un paquete por cada cadena secundaria, espera a que se confirme y repite el proceso. Las transacciones ya confirmadas se omiten en la reconstrucción, por lo que el bucle no tiene estado: los límites de velocidad, los fallos y los reinicios no suponen ningún coste; basta con volver a ejecutarlo para que continúe. Los contratiempos de Esplora se reintentan con un retroceso exponencial, en lugar de abortar una espera que puede prolongarse durante horas.
Hay dos detalles estructurales que son importantes:
Ejecutamos esta recuperación de forma secuencial (la configuración por defecto). Con FAN_OUT=1, el bucle habría difundido primero una transacción adicional con 4 salidas: los 9.388 sats de financiación se habrían dividido en un UTXO por cada hoja económica, cada uno con un tamaño equivalente a las comisiones CPFP restantes de esa hoja más un margen de 1.000 sats, y la última salida habría absorbido el resto. Una transacción P2WPKH de 1 entrada y 4 salidas ocupa unos 203 vB, lo que equivale a unos 203 sats a razón de 1 sat por vB.
A partir de ese momento, se desarrollan cuatro cadenas en paralelo: una por cada hoja. Cada cadena de hoja constituye su propio clúster TRUC, por lo que los cuatro paquetes de una ronda son independientes y pueden confirmarse en el mismo bloque. Cada una de las cuatro hojas de este monedero tenía una cadena de unos 6 paquetes antes de su reembolso:
Aproximadamente una ganancia de 3,5× en tiempo real para unos 203 sats, y la misma patrón se repite tras el vencimiento del reembolso: los cuatro paquetes de reembolso se transmiten en un solo bloque con UTXO por hoja, en lugar de en cuatro bloques consecutivos. La ganancia varía en función del número de hojas (suma de las profundidades de la cadena frente a la cadena más profunda), por lo que una cartera con docenas de hojas económicas siempre debería distribuirse en abanico; para un puñado de hojas, la secuencia es más sencilla y, en este caso, resultó lo suficientemente rápida.
Una vez finalizado el bucle, el reembolso de cada «hoja económica» espera a que expire su bloqueo temporal CSV. Al volver a ejecutar el mismo hacer, recuperar una vez vencido el plazo, se publican las devoluciones; en cuanto se confirmen, realizar un barrido crea y firma transacciones «spend» de ruta de clave Taproot de una sola entrada a partir de las salidas de reembolso hacia cualquier dirección de destino, y las transmite como transacciones normales. En el momento de redactar este artículo, las cuatro salidas económicas de este monedero se realizan a través de sus cadenas y están a la espera de que venza el plazo de reembolso.
Vale la pena ser preciso, porque es aquí donde los eslóganes publicitarios suelen adelantarse a los hechos:
No es un sistema sin confianza, y tampoco afirmamos que lo sea. En el espectro: es menos seguro que un Lightning , que por su propia naturaleza mantiene el estado de su propio canal; pero mucho más seguro que un custodio que guarda tanto tus claves como tus datos y cuya retención parece un servicio normal hasta que se produce la congelación de las retiradas.
Violación de TRUC y entradas que faltan como señales de secuenciación normales, y nunca consideres un lote de paquetes como algo que se envía y se olvida.Todo lo anterior se ha llevado a cabo manualmente con herramientas de código abierto. Ese es el lugar adecuado para demostrarlo, pero no es donde se mueven la mayoría de los usuarios. El objetivo hacia el que nos dirigimos es una vía de salida para la que el usuario nunca tenga que prepararse.
En concreto: la aplicación Blink actualizará y conservará el paquete de recuperación automáticamente tras cada cambio de estado —cada transacción—, guardándolo en el almacenamiento local del dispositivo y, si el usuario así lo configura, en su propio almacenamiento en la nube, cifrado. El paquete no conlleva ninguna autoridad de gasto; se trata de datos de salida, no de claves, por lo que realizar una copia de seguridad automática amplía la ventana de salida sin ampliar la superficie de ataque. La semilla sigue siendo el único secreto que hay que proteger, exactamente igual que hoy en día.
La cuestión es que la frase «siempre puedes salir» no debería requerir ninguna preparación. Sea cual sea la última transacción, los datos necesarios para devolver esos fondos a Bitcoin ya están guardados. Si no se puede acceder a Spark, la salida se ejecutará desde la propia aplicación de Blink o desde la herramienta de código abierto utilizando el mismo paquete; no habrá que apresurarse a recuperar el estado de operadores que quizá ya no estén disponibles.
Esto subsana la carencia que ponía de manifiesto este caso práctico: las herramientas pueden salir, pero solo si se comparan con un paquete completo y reciente. Automatizar la actualización para que esté siempre al día y siempre se guarde una copia de seguridad es lo que convierte una vía de evacuación que primero hay que construir en una que simplemente está ahí.
Todaslas herramientas utilizadas aquí son de código abierto, y el caso práctico técnico, con cada comando e ID de transacción, se encuentra junto al código:
Pruébalo con el paquete de tu propio monedero antes de que lo necesites. De eso se trata.

Empieza a recibir y enviar bitcoins ahora