BLOG |
Comment faire ?
Récupération de 100 000 sats grâce aux sauvegardes de la graine et de l'état.
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 :
« 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.
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.
Un portefeuille Spark mobile contenant 100 000 sats répartis sur 22 « leaves ».

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.
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.
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.
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.
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 :
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).
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 :
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 :
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.
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 :
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 :
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.
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.
Il convient d'être précis, car c'est souvent là que les discours marketing prennent le pas sur les faits :
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.
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 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.

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