Sauver ses bitcoins de la corruption
Par taz le lundi 8 septembre 2014, 07:00 - Économie - Lien permanent
Après avoir sauvé sa clef USB, voilà un potentiel moyen de sauver les bitcoins contenus dans un wallet.dat corrompu.
Lorsque le wallet.dat contient des erreurs, bitcoind refuse de le charger, et justifie cela par le message suivant :
EXCEPTION: St13runtime_error
CDB : Error 30, can't open database wallet.dat
bitcoin in Runaway exception
Cela traduit que suite à une erreur d'écriture ou à un quelconque dysfonctionnement, le fichier wallet.dat de bitcoind/bitcoin-qt/bitcoin-core est corrompu et illisible par le client. Or ce fichier contient entre autres toutes les clefs privées des adresses gérées par le portefeuille. 3 cas se présentent alors.
- Vous avez une copie de secours (backup) à jour du portefeuille ou un dump des clefs privées.
- Vous avez une copie de secours, mais pas à jour.
- Vous n'avez pas de copie de secours, ou la copie que vous avez s'avère aussi être corrompue.
Dans le premier cas, tout va bien, il suffit de restaurer la copie ou d'importer le dump.
Le second cas peut advenir lorsqu'entre la sauvegarde de la copie et la corruption du fichier ont eu lieu des transactions. Par exemple, la création d'une nouvelle adresse de réception suivie d'un transfert vers cette adresse. Mais aussi, un envoi de bitcoin peut conduire à cette situation : une adresse A disposant de 10 BTC envoyant 1 BTC vers l'extérieur B envoie simultanément 9 BTC à une adresse de change C qui vient se greffer au portefeuille (c'est une des subtilités du protocole bitcoin pour éviter la double dépense ceci afin de dissimuler dans l'écriture publique de la transaction quelle est la dépense et quel est le reste). Si uniquement la clef privée de A se trouve sur la sauvegarde et pas celle de C, au revoir les 9 BTC si la restauration écrase le fichier corrompu[1].
Dans le troisième cas, pas de backup, il ne vous reste plus que les yeux pour pleurer.
À moins que... bonne nouvelle, il est peut-être possible de récupérer quand même vos pièces virtuelles.
Note
[1] Pour remédier à cette dernière situation, les développeurs de bitcoin ont implémenté un key pool, une sorte de réserve d'adresses libres destinées à être utilisés dans le futur. Ces dernières sont enregistrées dans le portefeuille et la réserve est rechargée en nouvelles adresses à chaque copie de secours effectuée depuis le client.
Il est en effet envisageable que les clefs privées soient encore intactes dans le wallet.dat corrompu. Le défi, c'est de les récupérer.
Note : avant chaque manoeuvre il convient de préférence de comprendre ce qu'on fait et d'avoir préalablement effectué des copies de sauvegarde. L'auteur du Radjaïdjah Blog dégage toute responsabilité en cas de perte irrémédiable de bitcoins faisant suite à l'application de la méthode décrite ci-après.
bitcoin utilise Berkeley DB pour les traiter ses données, entre autres pour le stockage en local de la blockchain et des clefs privées. wallet.dat, fichier contenant :
- la paire clef publique / privée de chaque adresse réceptrice du portefeuille
- la liste des transactions effectuées vers ou depuis ces dernières
- la clef à utiliser par défaut
- certaines préférences de l'utilisateur
- la liste des adresses destinataires enregistrées
- des clefs de réserve (key pool)
- un numéro de version
est un exemple de base de données se conformant à ce format.
Lorsque l'option -salvagewallet
de bitcoind est incapable de restaurer le portefeuille de façon satisfaisante, reste à passer en mode plus manuel.
Extraction des données brutes concernant les clefs
Rappel : un couple clef privée / clef publique est une paire ECDSA, à la base de la cryptographie basée sur les courbes elliptiques (voir ici pour une très bonne explication).
La structure du fichier wallet.dat (une copie évidemment) peut être appréhendée avec un éditeur hexadécimal et un peu d'intuition :
00003e80: 1a01 01fd 1701 3082 0113 0201 0104 2044 ......0....... D 00003e90: b158 5492 c83e 83fd 9177 83c1 b74e c25e .XT..>...w...N.^ 00003ea0: 3e6b 3ca8 722c c41e abe0 5249 a462 88a0 >k<.r,....RI.b.. 00003eb0: 81a5 3081 a202 0101 302c 0607 2a86 48ce ..0.....0,..*.H. 00003ec0: 3d01 0102 2100 ffff ffff ffff ffff ffff =...!........... 00003ed0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 00003ee0: fffe ffff fc2f 3006 0401 0004 0107 0441 ...../0........A 00003ef0: 0479 be66 7ef9 dcbb ac55 a062 95ce 870b .y.f~....U.b.... 00003f00: 0702 9bfc db2d ce28 d959 f281 5b16 f817 .....-.(.Y..[... 00003f10: 9848 3ada 7726 a3c4 655d a4fb fc0e 1108 .H:.w&..e]...... 00003f20: a8fd 17b4 48a6 8554 199c 47d0 8ffb 10d4 ....H..T..G..... 00003f30: b802 2100 ffff ffff ffff ffff ffff ffff ..!............. 00003f40: ffff fffe baae dce6 af48 a03b bfd2 5e8c .........H.;..^. 00003f50: d036 4141 0201 01a1 4403 4200 04ff 5443 .6AA....D.B...TC 00003f60: fcd0 0b8f 3844 569d caa4 2662 d90e 0c8a ....8DV...&b.... 00003f70: fb1f e358 04c3 20e5 bb08 ea6d adc3 2317 ...X.. ....m..#. 00003f80: 67aa 13b9 dd6a bcb4 5a8c d049 7490 d5c2 g....j..Z..It... 00003f90: a78f f85e a617 1139 4b3b 08e5 c9bd ca00 ...^...9K;...... 00003fa0: 4600 0103 6b65 7941 04ff 5443 fcd0 0b8f F...keyA..TC.... 00003fb0: 3844 569d caa4 2662 d90e 0c8a fb1f e358 8DV...&b.......X 00003fc0: 04c3 20e5 bb08 ea6d adc3 2317 67aa 13b9 .. ....m..#.g... 00003fd0: dd6a bcb4 5a8c d049 7490 d5c2 a78f f85e .j..Z..It......^ 00003fe0: a617 1139 4b3b 08e5 c9c2 57f9 0400 0102 ...9K;....W.....
Il ressort (cf adresse 00003fa0
) que les clefs sont annoncées par keyA, et même plus précisément par la séquence d'octets 03 6b 65 79 41. En suivant l'approche développée ici, on peut supposer que d'une part chaque clef publique est encodée par les 65 octets suivant ce préfixe, et que d'autre part à chaque clef publique correspond une paire clef privée/clef publique située ailleurs dans le fichier et encodée par la même clef publique précédée de 217 octets. On retrouve cette correspondance dans le dump (db_dump wallet.dat
), où chaque clef publique est annoncée par 036b657941, le couple associé suivant immédiatement, avec le préfixe fd1701308201130201010420[1].
Dès lors on peut extraire tous les couples (clé publique / paire) grâce au script perl suivant (crédit : John Tobey) :
!/usr/bin/perl $_ = `cat wallet.dat`; while (/keyA(.{65})/sg) { $k{$1}++ } for my $k (keys(%k)) { while (/(\xfd.{216}\Q$k\E)/sg) { print("PUBKEYHEX=", unpack("H*", $k), "\n"); print("KEYPAIRHEX=", unpack("H*", $1), "\n\n"); } }
On obtient ainsi la liste des données brutes concernant les clefs stockées dans la base de données.
Identification des adresses à solde non nul
Cette étape n'est à proprement parler pas indispensable mais elle permet d'apprendre différentes façons de coder les clefs.
La liste obtenue à l'étape 1 est en général assez longue, une centaine de couples. Ceci est dû à la présence des adresses de réserve, le key pool. Pour en savoir un peu plus sur la façon d'encoder les clefs privées, voir ici. Concernant les clefs publiques, l'essentiel est qu'à partir de la clef publique on peut génèrer déterministiquement une unique adresse bitcoin, qui est en quelque sorte sa signature. À nouveau, le procédé est bien expliqué ici. En pratique cela est implémenté dans bitcointools, mais on peut également utiliser block explorer, même si c'est bien plus coûteux en vie privée.
En remplaçant les deux lignes de print
dans le script précédent par un unique print(unpack("H*", $k), "\n");
, on obtient un fichier pubkeylist présentant uniquement la liste des clefs publiques.
Puis :
#!/bin/sh cat pubkeylist|while read pubkey; do echo $pubkey hash=$(wget -q -O - http://blockexplorer.com/q/hashpubkey/$pubkey) sleep .1 add=$(wget -q -O - http://blockexplorer.com/q/hashtoaddress/$hash) sleep .1 balance=$(wget -q -O - http://blockexplorer.com/q/addressbalance/$add) sleep .1 echo $pubkey $add $balance >>summary done
On arrive ainsi à ainsi un tableau indiquant les adresses avec solde positif[2].
Création d'un nouveau portefeuille
À partir de là, pour chaque couple, il est possible de créer un nouveau portefeuille, de deux façons :
1re solution : effacer le portefeuille corrompu, en créer un nouveau avec le client. Dumper (db_dump
) les deux portefeuilles et comparer l'encodage des clefs. Si les formats sont identiques, il suffit alors d'injecter les couples d'adresses bénéficiaires du wallet.dat corrompu dans le nouveau portefeuille, de convertir (db_load
) le résultat en wallet.dat, et enfin de bitcoind -rescan
er ce dernier (mettre à jour les transactions des adresses du portefeuille depuis la blockchain). À vos risques et périls !
2e solution : Si les formats ne sont pas identiques lors de la comparaison précédente, il reste encore la possibilité de faire la même chose avec un client downgradé adapté, ou en dernier recours de créer un portefeuille manuellement de la façon suivante (crédit : John Tobey) :
#! /bin/sh PUBKEYHEX={your public key} KEYPAIRHEX={your full key pair} DB_LOAD=db_load RECOVERED_WALLET=recovered_wallet.dat # Refuse to modify a previously created file. test -e "$RECOVERED_WALLET" && { echo "$0: exiting because $RECOVERED_WALLET exists" 2>&1 exit 1 } $DB_LOAD "$RECOVERED_WALLET" <<EOF VERSION=3 format=bytevalue database=main type=btree db_pagesize=4096 HEADER=END 036b657941$PUBKEYHEX $KEYPAIRHEX 0776657273696f6e 027d0000 0a64656661756c746b6579 41$PUBKEYHEX DATA=END EOF
À noter que :
- ceci n'est qu'un exemple avec un format bien particulier
- le recovered_wallet.dat obtenu est minimaliste, il ne contient que les adresses concernées et la version[3]. Pas de transactions ni de key pool, donc attention avec les transferts partiels.
- Les lignes 036b657941$PUBKEYHEX et $KEYPAIRHEX peuvent être dupliquées avec tous les couples de clefs utiles.
bitcoind -rescan
permet alors si tout va bien de récupérer les bitcoins, il est alors raisonnable de les transférer vers des adresses externes sûres.
Recommandations générales
- Toujours faire un backup du portefeuille (
backupwallet
) vers deux emplacements différents après une transaction. Ne pas effacer les anciens backups avant d'être sûr que les actuels sont valides. - Ne jamais copier manuellement wallet.dat pendant que le serveur est lancé, cela ne crée pas une copie de secours fiable.
- Ne jamais lancer deux clients simultanément ou utiliser des portefeuilles contenant des données se recouvrant avec plusieurs clients
Conclusion
Le but de cette entrée est d'aider les personnes qui auraient un wallet.dat corrompu en leur proposant une approche pour récupérer leurs bitcoins. Pour rappel, il est très facile de perdre des bitcoins de façon irréversible, donc si vous avez le moindre doute, ne suivez pas cette méthode avant qu'il ne soit trop tard. Aucune des informations présentées n'est garantie. N'hésitez pas à faire part de vos remarques/questions/critiques avant d'entreprendre une opération risquée. Maintenant, si cette méthode a permis à un lecteur de récupérer des bitcoins au bord du gouffre, eh bien tant mieux.
Notes
[1] Dans les dumps des versions plus modernes (db_pagesize=16384 au lieu de 4096), l'encodage est différent et le préfixe de la paire est d63081d30201010420.
[2] Digression : conceptuellement les adresses de payement ont été conçues pour être à usage unique, c'est l'implémentation qui permet d'en avoir des usages multiples. Utiliser deux fois la même adresse, c'est techniquement et moralement mal !
[3] Ce minimalisme se traduit par un avertissement du client de la forme : Warning: error reading wallet.dat! All keys read correctly, but transaction data or address book entries might be missing or incorrect.
13 derniers coms