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