Skip to content

Commit 1542cd5

Browse files
committed
more documentation
1 parent ecd73ba commit 1542cd5

File tree

1 file changed

+84
-26
lines changed

1 file changed

+84
-26
lines changed

src/main/java/prof7bit/bitcoin/wallettool/fileformats/WalletDatHandler.xtend

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* (c) 2014 Bernd Kreuss
3+
*/
4+
15
package prof7bit.bitcoin.wallettool.fileformats
26

37
import com.google.bitcoin.core.ECKey
@@ -25,13 +29,17 @@ import org.spongycastle.crypto.params.ParametersWithIV
2529
import prof7bit.bitcoin.wallettool.core.KeyObject
2630
import prof7bit.bitcoin.wallettool.exceptions.FormatFoundNeedPasswordException
2731

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+
*/
2838
class WalletDatHandler extends AbstractImportExportHandler {
2939
val log = LoggerFactory.getLogger(this.class)
3040

3141
/**
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.
3543
*/
3644
override load(File file, String password, String password2) throws Exception {
3745

@@ -183,7 +191,7 @@ class WalletDat {
183191
val hash = key.readString
184192
val name = value.readString
185193
log.trace("found: type 'name' {} {}", hash, name)
186-
rawKeyList.addName(hash, name)
194+
rawKeyList.addLabel(hash, name)
187195
}
188196

189197
private def parseKey(ByteBuffer key, ByteBuffer value) {
@@ -300,12 +308,20 @@ class WalletDat {
300308
}
301309
}
302310

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+
*/
303316
def getKeys() {
304317
rawKeyList.keyData.values
305318
}
306319

320+
/**
321+
* get the label for a bitcoin address or "" if not found.
322+
*/
307323
def getAddrLabel(String addr){
308-
rawKeyList.getName(addr)
324+
rawKeyList.getLabel(addr)
309325
}
310326

311327

@@ -542,12 +558,35 @@ class BerkeleyDBHeaderPage extends BerkeleyDBPage {
542558

543559
/**
544560
* 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
546562
*/
547563
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+
*/
548573
public val Map<ByteBuffer, WalletDatRawKeyData> keyData = new HashMap
549-
public val Map<String, String> names = new HashMap
550574

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+
*/
551590
def findOrAddNew(byte[] pub){
552591
var key = getKeyData(pub)
553592
if (key == null){
@@ -558,12 +597,20 @@ class WalletDatRawKeyDataList {
558597
return key
559598
}
560599

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)
563606
}
564607

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)
567614
if (result == null){
568615
result = ""
569616
}
@@ -584,20 +631,35 @@ class WalletDatRawKeyDataList {
584631
key.private_key = unencrypted
585632
}
586633

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+
*/
587643
def addPoolFlag(byte[] pub){
588644
val key = findOrAddNew(pub)
589645
key.pool = true
590646
}
591647

592648
def clear(){
593649
keyData.clear
594-
names.clear
650+
labels.clear
595651
}
596652
}
597653

598654

599655
/**
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.
601663
*/
602664
class WalletDatRawKeyData {
603665
public var byte[] encrypted_private_key
@@ -660,17 +722,7 @@ class WalletDatCrypter {
660722
val plaintext = newByteArrayOfSize(size)
661723
val processLength = cipher.processBytes(ciphertext, 0, ciphertext.length, plaintext, 0)
662724
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)
674726
}
675727

676728
def getKeyParamFromPass(String pass, byte[] salt, int nDerivationIterations) throws UnsupportedEncodingException {
@@ -687,6 +739,11 @@ class WalletDatCrypter {
687739
cipher.init(forEnryption, key)
688740
}
689741

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+
*/
690747
def stretchPass(String pass, byte[] salt, int nDerivationIterations) throws UnsupportedEncodingException {
691748
var passbytes = pass.getBytes("UTF-8")
692749
var data = newByteArrayOfSize(64)
@@ -703,8 +760,9 @@ class WalletDatCrypter {
703760
}
704761

705762
/**
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))
708766
*/
709767
def doubleHash(byte[] data){
710768
val sha = new SHA256Digest

0 commit comments

Comments
 (0)