1
+ /**
2
+ * (c) 2014 Bernd Kreuss
3
+ */
4
+
1
5
package prof7bit.bitcoin.wallettool.fileformats
2
6
3
7
import com.google.bitcoin.core.ECKey
@@ -25,13 +29,17 @@ import org.spongycastle.crypto.params.ParametersWithIV
25
29
import prof7bit.bitcoin.wallettool.core.KeyObject
26
30
import prof7bit.bitcoin.wallettool.exceptions.FormatFoundNeedPasswordException
27
31
32
+ /**
33
+ * Wallet.dat import handler. This handler does not need any
34
+ * non-java dependency to read the Berkeley-db B-tree files.
35
+ * Reverse engineered with inspiration from pywallet.py,
36
+ * db_page.h, db_dump and a hex editor.
37
+ */
28
38
class WalletDatHandler extends AbstractImportExportHandler {
29
39
val log = LoggerFactory . getLogger(this . class)
30
40
31
41
/**
32
- * Try to parse keys from a bitcoin-core wallet.dat file.
33
- * Reverse engineered with inspiration from pywallet.py,
34
- * db_page.h, db_dump and a hex editor.
42
+ * Try to import keys and labels from a bitcoin-core wallet.dat file.
35
43
*/
36
44
override load (File file , String password , String password2 ) throws Exception {
37
45
@@ -183,7 +191,7 @@ class WalletDat {
183
191
val hash = key. readString
184
192
val name = value. readString
185
193
log. trace(" found: type 'name' {} {}" , hash, name)
186
- rawKeyList. addName (hash, name)
194
+ rawKeyList. addLabel (hash, name)
187
195
}
188
196
189
197
private def parseKey (ByteBuffer key , ByteBuffer val ue ) {
@@ -300,12 +308,20 @@ class WalletDat {
300
308
}
301
309
}
302
310
311
+ /**
312
+ * Return the collection of all WalletDatRawKeyData objects.
313
+ * This is used by the importer after the wallet has been
314
+ * completely parsed and decrypted.
315
+ */
303
316
def getKeys () {
304
317
rawKeyList. keyData. values
305
318
}
306
319
320
+ /**
321
+ * get the label for a bitcoin address or "" if not found.
322
+ */
307
323
def getAddrLabel (String addr ){
308
- rawKeyList. getName (addr)
324
+ rawKeyList. getLabel (addr)
309
325
}
310
326
311
327
@@ -542,12 +558,35 @@ class BerkeleyDBHeaderPage extends BerkeleyDBPage {
542
558
543
559
/**
544
560
* Maintains collections of raw key data objects
545
- * and names while they are parsed from wallet.dat
561
+ * and labels while they are parsed from wallet.dat
546
562
*/
547
563
class WalletDatRawKeyDataList {
564
+ /**
565
+ * the keys in this map are indexed by their public key
566
+ * since this is also the way Bitcoin handles it.
567
+ * Because we cannot directly use a byte[] as index of
568
+ * a hash map (byte[] lacks proper implementation of
569
+ * equals() and other comparable methods we are wrapping
570
+ * it into a ByteBuffer which is a lightweight and simple
571
+ * solution to this problem.
572
+ */
548
573
public val Map<ByteBuffer , WalletDatRawKeyData > keyData = new HashMap
549
- public val Map<String , String > names = new HashMap
550
574
575
+ /**
576
+ * we are collecting the labels in a separate map because
577
+ * they are indexed by bitcoin address rather than public
578
+ * key and at this stage (while still parsing stuff from
579
+ * the bdb file) its just simpler that way. May higher
580
+ * layers at a later time take care of combining them.
581
+ */
582
+ public val Map<String , String > labels = new HashMap
583
+
584
+ /**
585
+ * @param pub byte array with public key
586
+ * @return WalletDatRawKeyData object for this key
587
+ * or a newly created instance of such (that was
588
+ * added to the map) if it was not yet in the map.
589
+ */
551
590
def findOrAddNew (byte [] pub ){
552
591
var key = getKeyData(pub)
553
592
if (key == null ){
@@ -558,12 +597,20 @@ class WalletDatRawKeyDataList {
558
597
return key
559
598
}
560
599
561
- def addName (String hash , String name ){
562
- names. put(hash, name)
600
+ /**
601
+ * @param addr the bitcoin address
602
+ * @param label to assign to this address
603
+ */
604
+ def addLabel (String addr , String label ){
605
+ labels. put(addr, label)
563
606
}
564
607
565
- def getName (String hash ){
566
- var result = names. get(hash)
608
+ /**
609
+ * @param the bitcoin address
610
+ * @return the label or "" if not found.
611
+ */
612
+ def getLabel (String addr ){
613
+ var result = labels. get(addr)
567
614
if (result == null ){
568
615
result = " "
569
616
}
@@ -584,20 +631,35 @@ class WalletDatRawKeyDataList {
584
631
key. private_key = unencrypted
585
632
}
586
633
634
+ /**
635
+ * This adds a flag to the key to mark it as "reserve",
636
+ * this is done for all keys that are mentioned in the
637
+ * "pool" list in the wallet file, so we can later
638
+ * label them differently, otherwise there is nothing
639
+ * special about these keys, they are treated like all
640
+ * other keys, its up to the user to decide whether to
641
+ * use that label for anything or just ignore it.
642
+ */
587
643
def addPoolFlag (byte [] pub ){
588
644
val key = findOrAddNew(pub)
589
645
key. pool = true
590
646
}
591
647
592
648
def clear (){
593
649
keyData. clear
594
- names . clear
650
+ labels . clear
595
651
}
596
652
}
597
653
598
654
599
655
/**
600
- * raw key data for a single key parsed from wallet.dat
656
+ * Raw key data for a single key parsed from wallet.dat
657
+ * Objects of this type are stored in a map inside the
658
+ * WalletDatRawKeyDataList container which is populated
659
+ * during parsing the wallet. After completely reading
660
+ * and optionally decrypting the wallet this contains
661
+ * all information about a key with the only exception
662
+ * of label which is stored in a separate map.
601
663
*/
602
664
class WalletDatRawKeyData {
603
665
public var byte [] encrypted_private_key
@@ -660,17 +722,7 @@ class WalletDatCrypter {
660
722
val plaintext = newByteArrayOfSize(size)
661
723
val processLength = cipher. processBytes(ciphertext, 0 , ciphertext. length, plaintext, 0 )
662
724
val doFinalLength = cipher. doFinal(plaintext, processLength);
663
- return removePadding(plaintext, processLength + doFinalLength)
664
- }
665
-
666
- def removePadding (byte [] plaintext , int length ){
667
- if (length == plaintext. length){
668
- return plaintext
669
- } else {
670
- val result = newByteArrayOfSize(length)
671
- System . arraycopy(plaintext, 0 , result, 0 , length)
672
- return result
673
- }
725
+ return plaintext. take(processLength + doFinalLength)
674
726
}
675
727
676
728
def getKeyParamFromPass (String pass , byte [] salt , int nDerivationIterations ) throws UnsupportedEncodingException {
@@ -687,6 +739,11 @@ class WalletDatCrypter {
687
739
cipher. init(forEnryption, key)
688
740
}
689
741
742
+ /**
743
+ * concatenate passbytes and salt and then apply nDerivationIterations iterations of sha512
744
+ * and return a new byte array of 64 bytes containing the result. This is used for deriving
745
+ * key and iv from the wallet password.
746
+ */
690
747
def stretchPass (String pass , byte [] salt , int nDerivationIterations ) throws UnsupportedEncodingException {
691
748
var passbytes = pass. getBytes(" UTF-8" )
692
749
var data = newByteArrayOfSize(64 )
@@ -703,8 +760,9 @@ class WalletDatCrypter {
703
760
}
704
761
705
762
/**
706
- * apply sha256 twice
707
- * @return sha256(sha256(data))
763
+ * apply sha256 double hash
764
+ * @param data byte array to be hashed
765
+ * @return new byte array containing sha256(sha256(data))
708
766
*/
709
767
def doubleHash (byte [] data ){
710
768
val sha = new SHA256Digest
0 commit comments