BLOG |

Como fazer

Estudo de caso: uma saída unilateral real do Spark na rede principal do Bitcoin

Recuperando 100 mil sats com backups da semente e do estado.

Estudo de caso: uma saída unilateral real do Spark na rede principal do Bitcoin
13 de julho de 2026
openoms

Uma saída unilateral real da rede Spark: os números, as falhas e as lições. Tudo o que se segue ocorreu na mainnet do Bitcoin e pode ser verificado publicamente na cadeia de blocos.

Eu realizei uma saída forçada de uma carteira Blink sem custódia (usando o Spark) na rede principal do Bitcoin. Tudo o que foi necessário para a saída foi derivado da semente da carteira: o financiamento da taxa, a assinatura da transação e a limpeza final. O único elemento adicional é um pacote de recuperação, um instantâneo dos dados de saída da carteira salvo enquanto a rede Spark ainda estava acessível. Com esse pacote em mãos, a saída em si não exigiu a cooperação de ninguém.

Os números reais para uma carteira de 100.000 sat:

  • 22 folhas, 253 pacotes de transações para encerrar totalmente
  • Apenas 4 leaves (90% do valor) valiam a pena para a saída. As taxas teriam consumido os outros 18.
  • ~9,4 mil sats em taxas para recuperar 90,1 mil, um pacote por bloco por cadeia (uma regra do mempool v3/TRUC, explicada abaixo) e, em seguida, uma espera de bloqueio temporal de cerca de 10 dias

“Sem custódia” significa que você sempre pode ir embora. Mas a escada de incêndio é estreita. Aqui está a história completa, com custos, falhas e lições aprendidas.

Por que fizemos isso

“Sem custódia” é uma expressão fácil de incluir em textos de marketing. Queríamos que fosse algo que você pudesse verificar, não algo em que você fosse obrigado a acreditar. Por isso, pegamos uma carteira Blink de verdade, fingimos que a rede Spark havia desaparecido e transferimos todos os sat recuperáveis de volta para a blockchain do Bitcoin usando nada além da semente, um pacote de recuperação salvo e ferramentas de código aberto que qualquer pessoa pode executar. Esta postagem é a prova disso.

A carteira

Uma carteira móvel do Spark com 100.000 sats distribuídos por 22 folhas.

(ilustrativo)

‍‍Um brevecontexto para os leitores que ainda não conhecem o Spark: os fundos de uma carteira Spark ficam em uma árvore compartilhada na cadeia, gerenciada por um conjunto de operadores, e cada folha dessa árvore é um trecho distinto do saldo da carteira, com seu próprio caminho pré-assinado de volta ao Bitcoin. Saída unilateral significa transmitir esse caminho — a cadeia de saída da folha, composta por transações na cadeia, confirmando nível por nível ao longo da árvore — para forçar a folha a migrar para o Bitcoin sem a cooperação dos operadores.

Uma carteira Spark móvel contendo 100.000 sats distribuídos por 22 folhas. As carteiras Spark acumulam folhas por meio do uso normal (os pagamentos dividem e redividem a árvore), e cada folha possui sua própria cadeia de saída: as transações na cadeia que devem ser confirmadas, nível por nível, para forçar a folha a entrar no Bitcoin sem a cooperação dos operadores. Para essa carteira, isso significou 253 pacotes de transações distribuídos pelas 22 cadeias. Para 100 mil sats.

Passo 1: manter um pacote de recuperação atualizado

A recuperação apenas com a semente não é possível depois que os operadores do Spark ficam offline: as folhas atuais não podem ser identificadas apenas a partir da semente. A saída requer um pacote de recuperação — um instantâneo em JSON das folhas da carteira e de suas transações ancestrais — atualizado enquanto os operadores ainda estiverem online:

execute o comando `refresh-recovery-bundle` com SEED_FILE=../.spark-seed.txt e BUNDLE=../recovery-bundle.json

Primeira lição na prática: nosso primeiro pacote estava incompleto, sem que percebêssemos. O volume dos operadores query_nodes(include_parents=true) A API omite o nó raiz da árvore para árvores da mainnet legadas; portanto, cada uma das 22 cadeias de saída apresentava uma lacuna no topo, e a construção do pacote offline falhou com A cadeia de saída está incompleta. A recuperação dos ancestrais ausentes por ID de nó (o que contorna a omissão da raiz) resolveu o problema — o exportador agora faz isso automaticamente e se recusa a gravar um pacote com cadeias abertas.

Verifique as cadeias de ancestrais do seu pacote antes que você precise delas; um pacote incompleto descoberto durante uma interrupção no serviço é irrecuperável.

Etapa 2: financiamento das taxas a partir do mesmo fundo inicial

As transações de saída são pré-assinadas sem taxa e pagam as taxas por meio de âncoras efêmeras do CPFP; portanto, a saída precisa de um UTXO independente da L1 do Bitcoin para financiar os aumentos nas taxas. A ferramenta gera um endereço de financiamento dedicado a partir da própria semente da carteira (caminho m/8797556'/<account>/0: apenas o índice de finalidade é protegido; os índices de conta e endereço não são, de modo que uma carteira apenas para observação pode derivar e monitorar o endereço de depósito a partir de um xpub sem precisar de nenhuma chave privada):

execute o comando cpfp-address com os seguintes parâmetros: SEED_FILE=../.spark-seed.txt, BUNDLE=../recovery-bundle.json e FEE_RATE=1

A uma taxa de 1 sat/vB, foram necessários 78.573 sats para sair de todas as 22 folhas. Quase 79% do saldo da carteira foi gasto em taxas. Nós fizemos o depósito em 3ab20a4c…7262 antes de fazer os cálculos por folha. Mais detalhes sobre isso abaixo.

Etapa 3: a transmissão ingênua e por que ela falhou

Na primeira tentativa, todos os 253 pacotes foram empacotados, assinados e enviados consecutivamente por meio da chamada POST /txs/package do Esplora. O primeiro pacote foi confirmado (pai 16895bc8…9619, filho CPFP 384cdc6d…3b37). Os outros 252 foram rejeitados:

"error": "Violação do TRUC: a transação 16895bc8… excederia o limite de contagem de descendentes"

"error": "entradas-de-transações-inválidas-ausentes-ou-já-utilizadas"

As transações de saída do Spark são do tipo v3, também chamadas de TRUC (Topologically Restricted Until Confirmation, BIP 431). A política do mempool limita um cluster v3 a um pai não confirmado mais um filho. É isso que torna as transações de saída pré-assinadas e sem taxa passíveis de aumento de taxa com segurança, e também significa que cada nível de uma cadeia de saída deve ser confirmado antes que o próximo nível possa entrar no mempool. Uma cadeia de 15 pacotes leva pelo menos 15 blocos, independentemente de como seja enviada. Qualquer ferramenta que dispare pacotes consecutivamente deixará tudo após o primeiro pacote de cada cadeia sem processamento.

Passo 4: encerre apenas o que vale a pena encerrar

Antes de automatizar o ciclo, calculamos o preço de cada folha: as taxas do CPFP para sua cadeia mais a retirada final de ~111 vB, em relação ao valor da folha. O resultado para essa carteira real a 1 sat/vB:

folhasvalorcusto de saída
Econômico490.112 sats8.388 sats
Antieconômico (poeira)189.888 sats~69.000 sats

Quatro folhas (32.768 + 32.768 + 16.384 + 8.192 sats) representavam 90% do saldo. Os outros 18 — resíduos de atividades rotineiras da carteira, alguns tão pequenos quanto 1 sat — teriam custado, cada um, entre 2.100 e 4.600 sats para serem retirados: retirar tudo teria consumido cerca de 77,5 mil sats em taxas para recuperar 100 mil. A ferramenta agora calcula isso por folha e ignora as folhas não econômicas por padrão (INCLUDE_UNECONOMICAL=1 substitui essa configuração; os cálculos econômicos são testados por regressão com base nesse mesmo pacote, adaptado ao regtest).

Então, o que chega ao destino?

Acompanhando cada sat da carteira de 100.000 sats até a saída econômica a 1 sat/vB. Há dois montantes em jogo: o próprio saldo da carteira e os 9.388 sats de taxa que financiam a saída econômica, efetivamente consumidos do endereço de financiamento.

Os 100.000 sats da carteira:

sats
4 viagens econômicas, cancelamentos de passagens + reembolsos confirmados90,112
− taxas de varredura (4 × ~111 vB × 1 sat/vB)−444
chega ao endereço de destino89,668
poeira em 18 folhas que não valem a pena, deixadas para trás no Spark9,888

As transações de saída e reembolso do Spark são pré-assinadas sem taxa (as taxas são determinadas pelas âncoras do CPFP); portanto, cada saída de reembolso contém o valor total da folha; a única dedução do próprio saldo da carteira é a taxa final de varredura.

Os 9.388 sats de recursos para cobrir taxas foram retirados de:

sats
Aumentos nas taxas do CPFP nas quatro redes de saídas8,388
reserva de segurança, devolvida como troco no endereço de financiamento~1,000

Conclusão: 89.668 de 100.000 sats (~90%) chegam ao destino. O custo total da saída é de ~18.700 sats — 9.888 abandonados como “poeira”, 8.388 em taxas do CPFP, 444 em taxas de varredura — além da taxa na cadeia para a transação que financiou o endereço do CPFP inicialmente. Com taxas de comissão mais altas, todos os termos aumentam e mais “folhas” ficam abaixo do limiar econômico; a 10 sat/vB, essa carteira abandonaria mais duas “folhas” e pagaria dez vezes mais em taxas pelo restante.

Etapa 5: o ciclo automatizado de confirmação e continuação

O fluxo reescrito consiste em um único comando:

executar recuperação SEED_FILE=../.spark-seed.txt BUNDLE=../recovery-bundle.json NETWORK=mainnet FEE_RATE=1

A cada rodada, ele reconstrói os pacotes a partir do estado atual da cadeia, assina o aumento do CPFP com a semente, envia um pacote por cadeia folha, aguarda a confirmação e repete o processo. As transações já confirmadas são ignoradas na reconstrução, de modo que o loop não mantém estado: limites de taxa, travamentos e reinicializações não causam nenhum impacto — basta executar novamente e ele retoma o processo. Os problemas no Esplora são repetidos com recuo exponencial, em vez de interromper uma espera que pode se estender por horas.

Há dois detalhes estruturais importantes:

  1. As transações de reembolso são adiadas, não transmitidas. A última transação de cada cadeia (o reembolso que efetivamente entrega a folha à chave do usuário) contém um bloqueio de tempo CSV: cerca de 2.000 blocos (aproximadamente duas semanas) para folhas novas; as folhas renovadas desta carteira tinham um bloqueio de 1.400 blocos (cerca de 10 dias). O loop decodifica o bloqueio de cada reembolso, informa sua altura de vencimento e para quando restarem apenas reembolsos com bloqueio de tempo. Como os reembolsos nunca são transmitidos antecipadamente, a alteração no financiamento das taxas nunca é capturada por uma transação com bloqueio de tempo, e as folhas são escoadas sequencialmente por meio de um único UTXO de financiamento, sem impasse.
  2. O paralelismo é opcional. No modo “fan-out”, o financiamento é primeiro dividido em um UTXO por folha; em seguida, cada cadeia de folhas avança um bloco, em vez de se revezarem. Para esta carteira, isso teria reduzido a fase de transmissão de cerca de 4 horas para cerca de 70 minutos, por um custo adicional de aproximadamente 200 sats. O ganho varia de acordo com o número de folhas; para um pequeno número delas, o modo sequencial já era rápido o suficiente.

Que fan-out teria feito diferença aqui?

Executamos essa recuperação sequencialmente (padrão). Com FAN_OUT=1, o loop teria, primeiro, transmitido uma transação adicional com 4 saídas — os 9.388 sats de financiamento divididos em um UTXO por folha econômica, cada um com valor correspondente às taxas CPFP restantes daquela folha mais um buffer de 1.000 sats, sendo que a última saída absorveria o restante. Uma transação P2WPKH de 1 entrada/4 saídas tem cerca de 203 vB, ou seja, cerca de 203 sats a 1 sat/vB.

A partir daí, quatro cadeias correm em paralelo — uma por folha. Cada cadeia de folha é um cluster TRUC independente, de modo que todos os quatro pacotes de uma rodada são independentes e podem ser confirmados no mesmo bloco. Cada uma das quatro folhas dessa carteira tinha uma cadeia de aproximadamente seis pacotes antes de seu reembolso:

bloqueia até o tempo de espera do bloqueio por tempoa cerca de 10 min/quarteirão
Sequencial (padrão)4 folhas × ~6 pacotes ≈ 24 blocos~4 horas
FAN_OUT=11 (fan-out) + ~6 (cadeia mais profunda) ≈ 7 blocos~70 minutos

Um ganho de aproximadamente 3,5× em tempo real para cerca de 203 sats, e o mesmo padrão se repete após o vencimento do reembolso: os quatro pacotes de reembolso são transmitidos em um único bloco com UTXOs por folha, em vez de quatro blocos consecutivos. O ganho varia de acordo com o número de folhas (soma das profundidades da cadeia em comparação com a cadeia mais profunda isoladamente); portanto, uma carteira com dezenas de folhas econômicas deve sempre adotar a estratégia de dispersão; para um punhado de folhas, a abordagem sequencial é mais simples e, neste caso, foi rápida o suficiente.

Etapa 6: bloqueios temporários e, em seguida, varredura

Após o término do ciclo, o reembolso de cada folha econômica aguarda o término do bloqueio de tempo CSV. Ao executar novamente o mesmo fazer, recuperar após o vencimento, divulga os reembolsos; assim que forem confirmados, fazer uma varredura gera e assina transações de gasto com caminho de chave Taproot de uma entrada, a partir das saídas de reembolso para qualquer endereço de destino, e as transmite como transações comuns. No momento da redação deste artigo, as quatro saídas econômicas dessa carteira estão em suas respectivas cadeias e aguardam o vencimento do reembolso.

O modelo de confiança, explicado de forma simples

Vale a pena ser preciso, pois é aqui que o discurso de marketing costuma se antecipar aos fatos:

  • Os operadores nunca podem gastar seus fundos. Cada “folha” está vinculada a um agregado formado pela sua chave e pela deles; sua assinatura é necessária para cada movimento. Um roubo exigiria que todos os operadores, sem exceção, agissem em conluio. Basta um único operador honesto para tornar isso impossível, mesmo que ele esteja offline: o roubo requer a assinatura ativa deles, não apenas a disponibilidade. O fato de os operadores ficarem offline é uma falha totalmente diferente. Nada é roubado, o caminho cooperativo fica paralisado, e a saída descrita abaixo é a solução para isso.
  • A saída precisa de dados, uma vez a cada mudança de estado. O pacote de recuperação deve ser obtido enquanto os operadores estiverem online e não requer mais permissões do que as necessárias para uma transação: sempre que você tiver tempo disponível, poderá atualizá-lo. Tudo o que foi coberto pela sua última atualização permanece passível de saída para sempre, e a retenção é detectável imediatamente (sua atualização falha enquanto seus pagamentos são bem-sucedidos).
  • A saída em si não requer permissão. Com um pacote salvo, o empacotamento, a assinatura, a transmissão e a varredura não exigem nenhuma ação por parte dos operadores. Essa é a parte que essa recuperação comprovou na mainnet.

Não é um sistema sem confiança, e não afirmamos que seja. No espectro: mais fraco do que um nó Lightning, que mantém seu próprio estado de canal por definição, mas muito mais seguro do que um custodiante que mantém tanto suas chaves quanto seus dados e cuja retenção parece um serviço normal até o congelamento da retirada.

Conclusões

  • Uma saída unilateral é uma saída de emergência, não uma porta. 253 pacotes, um bloco por pacote por cadeia, um bloqueio temporal medido em semanas e taxas que teriam consumido 79% do saldo se aplicadas indiscriminadamente a uma carteira de 100 mil sat. Com a triagem econômica, cerca de 90% do saldo chegou ao destino. A autocustódia no Spark é real, mas o caminho de saída é caro e lento por natureza; ajuste suas expectativas de tamanho de acordo com isso.
  • As folhas de resíduo são um problema.18 das 22 folhas não valiam a pena serem descartadas, nem mesmo a 1 sat/vB. Desde essa recuperação, a ferramenta consolida as folhas em denominações ideais para descarte (por meio de trocas cooperativas, enquanto os operadores estão disponíveis) antes do descarte, de modo que as recuperações futuras descartam muito menos.
  • A atualização e a integridade do pacote são fundamentais. Sem um pacote de recuperação completo e recente, não há como encerrar o processo. Atualmente, as ferramentas o atualizam sempre que possível a cada execução; o próximo passo é incorporar a atualização automática ao aplicativo Blink.
  • Respeito ao TRUC. Um par não confirmado de pai+filho por cadeia. O equipamento de saída deve confirmar e continuar, tolerar Violação do TRUC e entradas ausentes como sinais de sequenciamento normais, e nunca trate um lote de pacotes como algo do tipo “enviar e esquecer”.
  • Tudo se origina da semente. A atualização do pacote, o financiamento da taxa, a assinatura do CPFP e a varredura final utilizaram a semente existente da carteira — não há chaves separadas para fazer backup, e o endereço de financiamento pode ser monitorado apenas como observação a partir de um xpub.

Outlook: a opção de sair, integrada ao aplicativo

Tudo o que foi descrito acima foi feito manualmente com ferramentas de código aberto. Esse é o lugar certo para comprovar isso, mas não é onde a maioria dos usuários está. O caminho que estamos traçando é uma saída para a qual o usuário nunca precise se preparar.

Concretamente: o aplicativo Blink atualizará e manterá o pacote de recuperação automaticamente após cada mudança de estado — a cada transação —, salvando-o no armazenamento local do dispositivo e, caso o usuário configure, em seu próprio armazenamento na nuvem, de forma criptografada. O pacote não possui autoridade para realizar transações; trata-se de dados de saída, não de chaves; portanto, fazer backup dele automaticamente amplia a janela de saída sem ampliar a superfície de ataque. A semente continua sendo o único segredo a ser protegido, exatamente como hoje.

A questão é que a frase “você sempre pode sair” não deveria exigir nenhuma preparação. Seja qual for a última transação realizada, os dados necessários para transferir esses fundos de volta para o Bitcoin já estão salvos. Se o Spark estiver inacessível, a saída será executada a partir do aplicativo Blink ou da ferramenta de código aberto, utilizando o mesmo pacote — sem a necessidade de correr para obter o estado de operadores que talvez já tenham saído do ar.

Isso resolve a lacuna que este estudo de caso revelou: a ferramenta pode ser encerrada, mas somente em relação a um pacote completo e recente. Automatizar a atualização para que ela esteja sempre atualizada e sempre tenha um backup é o que transforma uma saída de emergência que você precisa construir primeiro em uma que simplesmente já está lá.

‍Todasas ferramentas utilizadas aqui são de código aberto, e o estudo de caso técnico, com todos os comandos e IDs de transação, está disponível junto com o código:

Experimente com o pacote da sua própria carteira antes de precisar dele. É essa a ideia.

Você achou este artigo valioso? Dê uma gorjeta ao autor!

Você achou este artigo valioso? Dê uma gorjeta ao autor!

Componente de compartilhamento social

Baixe a Blink

Comece a receber e enviar bitcoin agora mesmo

Comunidade