BLOG |

Comment faire ?

Étude de cas : une véritable sortie unilatérale Spark sur le réseau principal Bitcoin

Récupération de 100 000 sats grâce aux sauvegardes de la graine et de l'état.

Étude de cas : une véritable sortie unilatérale Spark sur le réseau principal Bitcoin
13 juillet 2026
openoms

Une véritable sortie unilatérale du réseau Spark : les chiffres, les échecs et les enseignements à en tirer. Tout ce qui suit s'est produit sur le réseau principal de Bitcoin et peut être vérifié publiquement sur la blockchain.

J'ai procédé à une sortie forcée d'un portefeuille Blink non dépositaire (à l'aide de Spark) sur le réseau principal Bitcoin. Tout ce qui était nécessaire à cette sortie provenait de la graine du portefeuille : le financement des frais, la signature de la transaction et le transfert final. Le seul élément supplémentaire est un « bundle de récupération », c'est-à-dire un instantané des données de sortie du portefeuille enregistré alors que le réseau Spark était encore accessible. Une fois ce bundle en main, la sortie elle-même n'a nécessité la coopération de personne.

Les chiffres réels pour un portefeuille de 100 000 sat :

  • 22 départs, 253 dossiers de transaction à finaliser
  • Seules 4 actions (représentant 90 % de la valeur) justifiaient une sortie. Les frais auraient absorbé les 18 autres.
  • Environ 9,4 k sats de frais pour récupérer 90,1 k, un paquet par bloc et par chaîne (règle du mempool v3/TRUC, expliquée ci-dessous), puis un délai d'attente d'environ 10 jours

« Sans détention » signifie que vous pouvez partir à tout moment. Mais l'escalier de secours est étroit. Voici l'histoire complète, avec les coûts, les échecs et les leçons à en tirer.

Pourquoi avons-nous fait cela ?

Le terme « sans garde » est facile à glisser dans un texte marketing. Nous voulions que ce soit quelque chose que vous puissiez vérifier, et non pas simplement croire sur parole. Nous avons donc pris un véritable portefeuille Blink, imaginé que le réseau Spark avait disparu, et réintégré de force chaque sat récupérable sur la blockchain Bitcoin en utilisant uniquement la phrase de récupération, un ensemble de fichiers de récupération sauvegardé et des outils open source accessibles à tous. Cet article en est la preuve.

Le portefeuille

Un portefeuille Spark mobile contenant 100 000 sats répartis sur 22 « leaves ».

(à titre d'exemple)

‍‍Petitrappel pour les lecteurs qui découvrent Spark : les fonds d’un portefeuille Spark sont stockés dans une arborescence partagée sur la chaîne, gérée par un ensemble d’opérateurs, et chaque feuille de cette arborescence correspond à une partie distincte du solde du portefeuille, dotée de son propre chemin pré-signé menant à Bitcoin. Une « sortie unilatérale » consiste à diffuser ce chemin — la chaîne de sortie de la feuille, composée de transactions sur la chaîne et confirmée niveau par niveau tout au long de l’arborescence — afin de forcer la feuille à rejoindre Bitcoin sans la coopération des opérateurs.

Un portefeuille Spark mobile contenant 100 000 sats répartis sur 22 feuilles. Les portefeuilles Spark accumulent des feuilles au cours de leur utilisation normale (les paiements divisent et redivisent l’arborescence), et chaque feuille possède sa propre chaîne de sortie : les transactions sur la chaîne qui doivent être confirmées, niveau par niveau, pour forcer la feuille à rejoindre Bitcoin sans la coopération des opérateurs. Pour ce portefeuille, cela représentait 253 paquets de transactions répartis sur les 22 chaînes. Pour 100 000 sats.

Étape 1 : veiller à ce que le pack de récupération soit à jour

La récupération à partir de la graine seule n'est pas possible une fois que les opérateurs Spark sont hors ligne : les feuilles actuelles ne peuvent pas être identifiées à partir de la graine seule. La sortie nécessite un « recovery bundle » — un instantané JSON des feuilles du portefeuille et de leurs transactions ancêtres — mis à jour tant que les opérateurs sont encore en ligne :

exécuter la commande « refresh-recovery-bundle » avec les paramètres suivants : SEED_FILE=../.spark-seed.txt et BUNDLE=../recovery-bundle.json

Première leçon tirée de la pratique : notre premier lot était incomplètement, sans que nous nous en rendions compte. Le traitement en masse des opérateurs query_nodes(include_parents=true) L'API omet le nœud racine des arborescences du réseau principal héritées ; par conséquent, chacune des 22 chaînes de sortie présentait une lacune à son sommet, et la construction du paquet hors ligne a échoué avec le message d'erreur suivant : La chaîne de sortie est incomplète. Le problème a été résolu en récupérant les ancêtres manquants à l'aide de l'identifiant du nœud (ce qui permet de contourner le saut de la racine) : l'exportateur procède désormais automatiquement de la sorte et refuse de générer un fichier contenant des chaînes incomplètes.

Vérifiez les chaînes d'ancêtres de votre bundle avant d'en avoir besoin ; un bundle incomplet détecté lors d'une interruption de service est irrécupérable.

Étape 2 : financement des frais à partir du même capital d'amorçage

Les transactions de sortie sont pré-signées sans frais et les frais sont réglés via des ancres éphémères CPFP ; la sortie nécessite donc un UTXO Bitcoin L1 indépendant pour financer les augmentations de frais. L'outil génère une adresse de financement dédiée à partir de la graine du portefeuille elle-même (chemin m/8797556'/<account>/0: seul l'index « purpose » est sécurisé ; les index « account » et « address » ne le sont pas. Ainsi, un portefeuille en lecture seule peut déduire et surveiller l'adresse de financement à partir d'un xpub sans aucune clé privée) :

créez le fichier cpfp-address avec les paramètres suivants : SEED_FILE=../.spark-seed.txt, BUNDLE=../recovery-bundle.json, FEE_RATE=1

À raison de 1 sat/vB, il fallait 78 573 sats pour sortir des 22 feuilles. Près de 79 % du solde du portefeuille en frais. Nous avons financé l'opération 3ab20a4c…7262 avant de faire le calcul pour chaque feuille. Plus d'informations à ce sujet ci-dessous.

Étape 3 : la diffusion naïve, et pourquoi elle a échoué

Lors de la première tentative, les 253 paquets ont été regroupés, signés et soumis les uns à la suite des autres via l'API POST /txs/package d'Esplora. Le premier paquet a été validé (parent 16895bc8…9619, enfant CPFP 384cdc6d…3b37). Les 252 autres ont été rejetés :

« error » : « Violation de la règle TRUC : la transaction 16895bc8… dépasserait la limite du nombre de descendants »

"error" : "bad-txns-inputs-missingorspent"

Les transactions de sortie Spark sont de type v3, également appelées TRUC (Topologically Restricted Until Confirmation, BIP 431). La politique du mempool limite un cluster v3 à un parent non confirmé plus un enfant. C’est ce qui permet d’augmenter en toute sécurité les frais des transactions de sortie pré-signées sans frais, et cela signifie également que chaque niveau d’une chaîne de sortie doit être confirmé avant que le niveau suivant puisse entrer dans le mempool. Une chaîne de 15 paquets nécessite au moins 15 blocs, quelle que soit la manière dont elle est soumise. Tout outil qui envoie les paquets les uns à la suite des autres bloquera tout ce qui se trouve après le premier paquet de chaque chaîne.

Étape 4 : ne quittez que ce qui en vaut la peine

Avant d'automatiser la boucle, nous avons évalué chaque feuille : les frais CPFP pour sa chaîne, plus le balayage final d'environ 111 vB, par rapport à la valeur de la feuille. Voici le résultat pour ce portefeuille réel, à un taux de 1 sat/vB :

feuillesvaleurcoût de sortie
Économique490 112 sats8 388 sats
Non rentable (poussière)189 888 satsenviron 69 000 sats

Quatre branches (32 768 + 32 768 + 16 384 + 8 192 sats) représentaient 90 % du solde. Les 18 autres — des résidus issus de l’activité courante du portefeuille, certains aussi petits qu’un sat — auraient chacun coûté entre 2 100 et 4 600 sats à retirer : retirer l’ensemble aurait coûté environ 77,5 k sats de frais pour récupérer 100 k. L’outil calcule désormais cela par feuille et ignore par défaut les feuilles non rentables (INCLUDE_UNECONOMICAL=1 permet de passer outre ; les calculs de rentabilité sont testés par régression sur ce paquet précis, adapté à regtest).

Alors, qu'est-ce qui arrive à destination ?

Après chaque sat du portefeuille de 100 000 sats via la sortie économique à 1 sat/vB. Deux montants sont en jeu : le solde du portefeuille lui-même, et les 9 388 sats de frais finançant la sortie économique, qui ont en réalité été prélevés sur l'adresse de financement.

Les 100 000 sats du portefeuille :

sats
4 billets d'avion à prix avantageux, chaînes de sortie + remboursements confirmés90,112
− frais de balayage (4 × environ 111 vB × 1 sat/vB)−444
arrive à l'adresse de destination89,668
de la poussière sur 18 feuilles inutiles, laissées derrière dans Spark9,888

Les transactions de sortie et de remboursement Spark sont pré-signées sans frais (les frais sont indexés sur les ancrages CPFP) ; ainsi, chaque sortie de remboursement porte la valeur totale de la feuille ; la seule déduction effectuée sur le solde du portefeuille est le frais de balayage final.

Les 9 388 sats provenant des frais ont été prélevés sur :

sats
Augmentation des frais du CPFP dans les quatre chaînes de sortie8,388
marge de sécurité, reversée sous forme de monnaie à l'adresse de financement~1,000

En résumé : 89 668 sats sur 100 000 (environ 90 %) parviennent à destination. Le coût total de la sortie s'élève à environ 18 700 sats — 9 888 abandonnés sous forme de « dust », 8 388 en frais CPFP, 444 en frais de balayage — auxquels s'ajoutent les frais sur la chaîne pour la transaction qui a initialement alimenté l'adresse CPFP. Lorsque les taux de frais augmentent, chaque terme s’accroît et davantage de « feuilles » passent sous le seuil de rentabilité ; à 10 sat/vB, ce portefeuille abandonnerait deux « feuilles » supplémentaires et paierait dix fois plus de frais sur les autres.

Étape 5 : la boucle automatisée « confirmer et continuer »

Le flux remanié se résume à une seule commande :

make recover SEED_FILE=../.spark-seed.txt BUNDLE=../recovery-bundle.json NETWORK=mainnet FEE_RATE=1

À chaque itération, le système reconstruit les paquets à partir de l’état actuel de la chaîne, signe l’augmentation du CPFP avec la graine, soumet un paquet par chaîne feuille, attend la confirmation, puis recommence. Les transactions déjà confirmées sont ignorées lors de la reconstruction ; la boucle est donc sans état : les limitations de débit, les plantages et les redémarrages n’ont aucune incidence — il suffit de relancer le processus pour qu’il reprenne là où il s’était arrêté. En cas de problèmes avec Esplora, le système effectue de nouvelles tentatives avec un recul exponentiel plutôt que d’interrompre une attente pouvant durer plusieurs heures.

Deux détails structurels sont importants :

  1. Les transactions de remboursement sont différées, et non diffusées. La dernière transaction de chaque chaîne (le remboursement qui transfère effectivement la feuille à la clé de l’utilisateur) comporte un verrou temporel CSV : environ 2 000 blocs (soit environ deux semaines) pour les feuilles neuves ; les feuilles renouvelées de ce portefeuille comportaient un verrou de 1 400 blocs (environ 10 jours). La boucle décode le verrou de chaque remboursement, indique sa hauteur d’échéance et s’arrête lorsqu’il ne reste plus que des remboursements soumis à un verrou temporel. Comme les remboursements ne sont jamais diffusés prématurément, la modification du financement des frais n’est jamais capturée par une transaction soumise à un verrou temporel, et les feuilles s’épuisent séquentiellement sur un seul UTXO de financement sans blocage.
  2. Le parallélisme est facultatif. En mode « fan-out », les fonds sont d’abord répartis en un UTXO par « feuille », après quoi chaque chaîne de feuilles avance d’un bloc à la fois, au lieu de procéder à tour de rôle. Pour ce portefeuille, cela aurait permis de réduire la phase de diffusion d’environ 4 heures à environ 70 minutes, pour un surcoût d’environ 200 sats. Le gain est proportionnel au nombre de feuilles ; pour une poignée de feuilles, le mode séquentiel était suffisamment rapide.

Quel fan-out aurait changé la donne ici ?

Nous avons exécuté cette récupération de manière séquentielle (configuration par défaut). Avec FAN_OUT=1, la boucle aurait d’abord diffusé une transaction supplémentaire comportant 4 sorties : les 9 388 sats de financement répartis en un UTXO par feuille économique, chacun d’une taille correspondant aux frais CPFP restants de cette feuille, plus une marge de 1 000 sats, la dernière sortie absorbant le reste. Une transaction P2WPKH à 1 entrée et 4 sorties pèse environ 203 vB, soit environ 203 sats à raison de 1 sat/vB.

À partir de là, quatre chaînes évoluent en parallèle — une par « feuille ». Chaque chaîne de « feuille » constitue son propre cluster TRUC ; ainsi, les quatre paquets d’un tour sont indépendants et peuvent être validés dans le même bloc. Chacune des quatre « feuilles » de ce portefeuille comptait environ six paquets avant son remboursement :

blocs jusqu'à l'expiration du délai d'attenteà environ 10 min par pâté de maisons
Séquentiel (par défaut)4 feuilles × environ 6 paquets ≈ 24 blocsenviron 4 heures
FAN_OUT=11 (fan-out) + ~6 (chaîne la plus profonde) ≈ 7 blocsenviron 70 minutes

On obtient un gain d'environ 3,5× en temps réel pour environ 203 sats, et le même schéma se répète après l'échéance du remboursement : les quatre lots de remboursement sont diffusés en un seul bloc avec des UTXO par « feuille », au lieu de quatre blocs consécutifs. Le gain est proportionnel au nombre de « feuilles » (somme des profondeurs de chaîne par rapport à la chaîne la plus profonde), donc un portefeuille comportant des dizaines de « feuilles » économiques devrait toujours opter pour la diffusion en éventail ; pour une poignée de « feuilles », la diffusion séquentielle est plus simple et s'est avérée suffisamment rapide dans ce cas.

Étape 6 : verrouillage temporel, puis balayage

Une fois la boucle terminée, le remboursement de chaque « feuille économique » attend l'expiration de son délai de verrouillage CSV. En relançant la même récupérer une fois la date d'échéance passée, les remboursements sont diffusés ; dès qu'ils sont confirmés, effectuer un balayage Il génère et signe des dépenses « Taproot key-path » à une seule entrée à partir des sorties de remboursement vers n’importe quelle adresse de destination, puis les diffuse sous forme de transactions ordinaires. À la date de rédaction de cet article, les quatre sorties économiques de ce portefeuille passent par leurs chaînes respectives et sont en attente de l’échéance du remboursement.

Le modèle de confiance, en termes simples

Il convient d'être précis, car c'est souvent là que les discours marketing prennent le pas sur les faits :

  • Les opérateurs ne peuvent en aucun cas dépenser vos fonds. Chaque « feuille » est verrouillée par un agrégat de votre clé et de la leur ; votre signature est requise pour chaque mouvement. Un vol nécessiterait la complicité de tous les opérateurs sans exception. La présence d’un seul opérateur honnête rend cela impossible, même s’il est hors ligne : le vol nécessite sa signature active, et non sa disponibilité. La mise hors ligne d’opérateurs constitue un type de défaillance totalement différent. Rien n’est volé, le chemin coopératif se fige, et la solution à ce problème se trouve ci-dessous.
  • La sortie nécessite des données, à chaque changement d'état. Le paquet de récupération doit être récupéré tant que les opérateurs sont en ligne, et cela ne nécessite pas plus d'autorisations que pour les transactions : vous pouvez l'actualiser dès que vous en avez le temps. Tout ce qui a été pris en compte lors de votre dernière actualisation reste valable indéfiniment, et toute rétention est immédiatement détectable (votre actualisation échoue alors que vos paiements aboutissent).
  • La sortie en elle-même ne nécessite aucune autorisation. Une fois le bundle enregistré, les opérations de packaging, de signature, de diffusion et de balayage ne nécessitent aucune intervention de la part des opérateurs. C'est la moitié de ce que cette mise à jour a démontré sur le mainnet.

Ce n'est pas un système « sans tiers de confiance », et nous ne prétendons pas qu'il le soit. Sur l'échelle de sécurité : moins sûr qu'un Lightning qui conserve par nature l'état de son propre canal, mais bien plus sûr qu'un dépositaire qui détient à la fois vos clés et vos données, et dont la rétention des fonds passe pour un service normal jusqu'au gel des retraits.

Points à retenir

  • Une sortie unilatérale est une issue de secours, pas une porte. 253 paquets, un bloc par paquet et par chaîne, un délai de verrouillage de plusieurs semaines, et des frais qui auraient englouti 79 % du solde s’ils avaient été appliqués sans distinction à un portefeuille de 100 000 sat. Grâce au triage économique, environ 90 % du solde a atteint sa destination. L'auto-custode sur Spark est bien réelle, mais la voie de sortie est, de par sa conception, coûteuse et lente ; il faut donc adapter ses attentes en conséquence.
  • Les feuilles « poussière » constituent un handicap.Sur 22 feuilles, 18 ne valaient pas la peine d’être retirées, même à un taux de 1 sat/vB. Depuis cette récupération, l’outil regroupe les feuilles en lots optimaux pour le retrait (via des échanges coopératifs, tant que les opérateurs sont joignables) avant de les retirer ; ainsi, les futures récupérations entraînent beaucoup moins d’abandons.
  • La fraîcheur et l'exhaustivité du bundle sont essentielles. Sans un bundle de restauration complet et récent, il n'y a rien à exporter. L'outil le met à jour de manière opportuniste à chaque exécution ; la prochaine étape consiste à intégrer cette mise à jour automatique dans l'application Blink.
  • Respectez TRUC. Un seul couple parent-enfant non confirmé par chaîne. L'outillage de sortie doit appliquer le principe « confirmer et continuer » et tolérer Violation du principe TRUC et données manquantes comme des signaux de séquencement normaux, et ne considérez jamais un lot de colis comme une opération « fire-and-forget ».
  • Tout part de la graine. La mise à jour du bundle, le financement des frais, la signature du CPFP et le balayage final ont tous utilisé la graine existante du portefeuille — aucune clé distincte à sauvegarder, et l'adresse de financement est accessible en lecture seule à partir d'un xpub.

Outlook : la fonction « Quitter », intégrée à l'application

Tout ce qui précède a été réalisé manuellement à l'aide d'outils open source. C'est certes le cadre idéal pour le démontrer, mais ce n'est pas celui dans lequel évoluent la plupart des utilisateurs. Notre objectif est de mettre en place une solution de secours à laquelle l'utilisateur n'aura jamais à se préparer.

Concrètement : l’application Blink actualisera et conservera automatiquement le « recovery bundle » après chaque changement d’état — à chaque transaction — en l’enregistrant dans la mémoire locale de l’appareil et, si l’utilisateur l’a configuré ainsi, dans son propre espace de stockage cloud, sous forme chiffrée. Ce « recovery bundle » ne confère aucun pouvoir de dépense ; il s’agit de données de sortie, et non de clés. Sa sauvegarde automatique élargit donc la fenêtre de sortie sans pour autant augmenter la surface d’attaque. La graine reste le seul secret à protéger, exactement comme aujourd’hui.

Le fait est que l'opération « vous pouvez toujours partir » ne devrait nécessiter aucune préparation. Quelle que soit la dernière transaction effectuée, les données nécessaires pour réinjecter ces fonds dans Bitcoin sont déjà enregistrées. Si Spark est inaccessible, la sortie s'effectuera depuis l'application Blink ou depuis l'outil open source, en utilisant le même ensemble de données — pas besoin de se démener pour récupérer l'état auprès d'opérateurs qui ont peut-être déjà disparu.

Cela comble ainsi la lacune mise en évidence par cette étude de cas : l'outil peut fonctionner, mais uniquement à partir d'un ensemble complet et récent. Automatiser la mise à jour pour qu'elle soit toujours à jour et systématiquement sauvegardée, c'est ce qui transforme une issue de secours qu'il faut d'abord construire en une issue qui est tout simplement là.

‍Tousles outils utilisés ici sont open source, et l'étude de cas technique, qui répertorie chaque commande et chaque identifiant de transaction, se trouve à côté du code :

Essayez-le avec le bundle de votre propre portefeuille avant d'en avoir besoin. C'est justement le but.

Vous avez trouvé cela utile ? Donnez un pourboire à l'auteur !

Vous avez trouvé cela utile ? Donnez un pourboire à l'auteur !

Composant de partage social

Téléchargez Blink

Commencez à recevoir et à envoyer des bitcoins dès maintenant

Communauté