BLOG |

How-tos

Caso práctico: una salida unilateral real de Spark en la red principal de Bitcoin

Recuperación de 100 000 sats con copias de seguridad de la semilla y del estado.

Caso práctico: una salida unilateral real de Spark en la red principal de Bitcoin
13 de julio de 2026
openoms

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:

  • Quedan 22 hojas y 253 paquetes de transacciones para completar la salida
  • Solo 4 hojas (el 90 % del valor) merecían la pena para salir. Las comisiones se habrían llevado las otras 18.
  • ~9,4 mil sats en comisiones para recuperar 90,1 mil, un paquete por bloque por cadena (una regla del mempool de v3/TRUC, que se explica más adelante) y, a continuación, una espera con bloqueo temporal de unos 10 días

«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.

Por qué lo hicimos

«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.

La cartera

Una cartera móvil de Spark que contiene 100 000 sats repartidos en 22 hojas.

(a modo de ejemplo)

‍‍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.

Paso 1: mantener actualizado el paquete de recuperación

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.

Paso 2: financiación de las comisiones con fondos de la misma ronda inicial

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.

Paso 3: la difusión ingenua y por qué fracasó

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.

Paso 4: sal solo de lo que merezca la pena

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:

hojasvalorcoste de salida
Económico490 112 sats8.388 sats
Poco rentable (polvo)189.888 sats~69 000 sats

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).

Entonces, ¿qué llega a su destino?

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:

sats
4 billetes económicos, cadenas de salida + reembolsos confirmados90,112
− comisiones por barrido (4 × ~111 vB × 1 sat/vB)−444
llega a la dirección de destino89,668
polvo en 18 hojas que no valen la pena, abandonadas en Spark9,888

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:

sats
Aumentos de las tarifas del CPFP en las cuatro cadenas de salida8,388
reserva de seguridad; se devuelve como cambio en la dirección de financiación~1,000

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.

Paso 5: el bucle automático de «confirmar y continuar»

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:

  1. Las transacciones de reembolso se aplazan, no se emiten. La última transacción de cada cadena (el reembolso que realmente entrega la hoja a la clave del usuario) lleva un bloqueo temporal CSV: unas 2.000 bloques (aproximadamente dos semanas) para las hojas nuevas; las hojas renovadas de este monedero llevaban 1.400 bloques (unos 10 días). El bucle descodifica el bloqueo de cada reembolso, informa de su altura de vencimiento y se detiene cuando solo quedan reembolsos con bloqueo temporal. Dado que los reembolsos nunca se transmiten antes de tiempo, el cambio de financiación de las comisiones nunca queda capturado por una transacción con bloqueo temporal, y las hojas se agotan secuencialmente a través de un único UTXO de financiación sin que se produzca ningún bloqueo.
  2. El paralelismo es opcional. En el modo «fan-out», primero se divide la financiación en un UTXO por cada hoja, tras lo cual cada cadena de hojas avanza un bloque en lugar de hacerlo por turnos. En el caso de este monedero, eso habría reducido la fase de difusión de unas 4 horas a unos 70 minutos, por unos 200 sats adicionales. La ganancia aumenta proporcionalmente al número de hojas; con unas pocas hojas, el modo secuencial ya era lo suficientemente rápido.

¿Qué distribución de salidas habría cambiado aquí?

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:

bloqueos hasta que finalice la espera por bloqueo temporala unos 10 min/manzana
Secuencial (predeterminado)4 hojas × ~6 paquetes ≈ 24 bloques~4 horas
FAN_OUT=11 (ramificación) + ~6 (cadena más profunda) ≈ 7 bloquesunos 70 minutos

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.

Paso 6: bloqueos temporales y, a continuación, barrido

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.

El modelo de confianza, explicado de forma sencilla

Vale la pena ser preciso, porque es aquí donde los eslóganes publicitarios suelen adelantarse a los hechos:

  • Los operadores nunca pueden gastar tus fondos. Cada «hoja» está vinculada a un agregado formado por tu clave y la de ellos; se requiere tu firma para cada movimiento. Para que se produjera un robo, sería necesario que todos y cada uno de los operadores se confabulasen. La presencia de un solo operador honesto lo hace imposible, incluso si está desconectado: el robo requiere su firma activa, no su disponibilidad. Que los operadores se desconecten es un fallo totalmente distinto. No se lleva nada, la ruta cooperativa se bloquea y la salida que se indica a continuación es la solución a ello.
  • La salida necesita datos, una vez por cada cambio de estado. El paquete de recuperación debe obtenerse mientras los operadores estén en línea, y no requiere más permisos que los que necesita una transacción: siempre que tengas tiempo, puedes actualizarlo. Todo lo que quede cubierto por tu última actualización seguirá siendo susceptible de salida para siempre, y cualquier retención se detecta de inmediato (tu actualización falla mientras que tus pagos se realizan con éxito).
  • La salida en sí misma no requiere autorización. Con un paquete guardado, el empaquetado, la firma, la difusión y la limpieza no requieren ninguna intervención por parte de los operadores. Esa es la parte que esta recuperación ha demostrado en la red principal.

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.

Conclusiones

  • Una salida unilateral es una salida de emergencia, no una puerta. 253 paquetes, un bloque por paquete por cadena, un bloqueo temporal que se mide en semanas y comisiones que habrían consumido el 79 % del saldo si se hubieran aplicado de forma indiscriminada a una cartera de 100 000 sat. Gracias a la selección económica, aproximadamente el 90 % del saldo llegó a su destino. La autocustodia en Spark es una realidad, pero la vía de salida es cara y lenta por su propia naturaleza; hay que ajustar las expectativas de tamaño en consecuencia.
  • Las hojas de polvo son un lastre.18 de las 22 hojas no merecían la pena ser eliminadas ni siquiera a 1 sat/vB. Desde esta recuperación, la herramienta agrupa las hojas en denominaciones óptimas para su eliminación (mediante intercambios cooperativos, mientras los operadores están disponibles) antes de eliminarlas, por lo que las recuperaciones futuras abandonan muchas menos.
  • La clave está en que el paquete esté actualizado y completo. Sin un paquete de recuperación completo y reciente, no hay nada que se pueda exportar. Actualmente, la herramienta lo actualiza de forma oportunista en cada ejecución; el siguiente paso es integrar la actualización automática en la aplicación Blink.
  • Respeta a TRUC. Un par no confirmado de «padre» y «hijo» por cadena. El equipo de salida debe aplicar el protocolo «confirmar y continuar» y tolerar 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 se deriva de la semilla. La actualización del paquete, la financiación de las comisiones, la firma del CPFP y el barrido final utilizaron la semilla existente de la cartera: no hay que hacer copias de seguridad de claves independientes, y la dirección de financiación solo se puede supervisar a través de un xpub.

Outlook: la función «Salir», integrada en la aplicación

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.

¿Te ha parecido valioso? Dé una propina al autor.

¿Te ha parecido valioso? Dé una propina al autor.

Componente Social Share

Descarga Blink

Empieza a recibir y enviar bitcoins ahora

Comunidad