--- /dev/null
+#
+# Workalike python implementation of Bitcoin's CDataStream class.
+#
+import struct
+import StringIO
+import mmap
+
+class SerializationError(Exception):
+ """ Thrown when there's a problem deserializing or serializing """
+
+class BCDataStream(object):
+ def __init__(self):
+ self.input = None
+ self.read_cursor = 0
+
+ def clear(self):
+ self.input = None
+ self.read_cursor = 0
+
+ def write(self, bytes): # Initialize with string of bytes
+ if self.input is None:
+ self.input = bytes
+ else:
+ self.input += bytes
+
+ def map_file(self, file, start): # Initialize with bytes from file
+ self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
+ self.read_cursor = start
+ def seek_file(self, position):
+ self.read_cursor = position
+ def close_file(self):
+ self.input.close()
+
+ def read_string(self):
+ # Strings are encoded depending on length:
+ # 0 to 252 : 1-byte-length followed by bytes (if any)
+ # 253 to 65,535 : byte'253' 2-byte-length followed by bytes
+ # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
+ # ... and the Bitcoin client is coded to understand:
+ # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
+ # ... but I don't think it actually handles any strings that big.
+ if self.input is None:
+ raise SerializationError("call write(bytes) before trying to deserialize")
+
+ try:
+ length = self.read_compact_size()
+ except IndexError:
+ raise SerializationError("attempt to read past end of buffer")
+
+ return self.read_bytes(length)
+
+ def write_string(self, string):
+ # Length-encoded as with read-string
+ self.write_compact_size(len(string))
+ self.write(string)
+
+ def read_bytes(self, length):
+ try:
+ result = self.input[self.read_cursor:self.read_cursor+length]
+ self.read_cursor += length
+ return result
+ except IndexError:
+ raise SerializationError("attempt to read past end of buffer")
+
+ return ''
+
+ def read_boolean(self): return self.read_bytes(1)[0] != chr(0)
+ def read_int16(self): return self._read_num('<h')
+ def read_uint16(self): return self._read_num('<H')
+ def read_int32(self): return self._read_num('<i')
+ def read_uint32(self): return self._read_num('<I')
+ def read_int64(self): return self._read_num('<q')
+ def read_uint64(self): return self._read_num('<Q')
+
+ def write_boolean(self, val): return self.write(chr(1) if val else chr(0))
+ def write_int16(self, val): return self._write_num('<h', val)
+ def write_uint16(self, val): return self._write_num('<H', val)
+ def write_int32(self, val): return self._write_num('<i', val)
+ def write_uint32(self, val): return self._write_num('<I', val)
+ def write_int64(self, val): return self._write_num('<q', val)
+ def write_uint64(self, val): return self._write_num('<Q', val)
+
+ def read_compact_size(self):
+ size = ord(self.input[self.read_cursor])
+ self.read_cursor += 1
+ if size == 253:
+ size = self._read_num('<H')
+ elif size == 254:
+ size = self._read_num('<I')
+ elif size == 255:
+ size = self._read_num('<Q')
+ return size
+
+ def write_compact_size(self, size):
+ if size < 0:
+ raise SerializationError("attempt to write size < 0")
+ elif size < 253:
+ self.write(chr(size))
+ elif size < 2**16:
+ self.write('\xfd')
+ self._write_num('<H', size)
+ elif size < 2**32:
+ self.write('\xfe')
+ self._write_num('<I', size)
+ elif size < 2**64:
+ self.write('\xff')
+ self._write_num('<Q', size)
+
+ def _read_num(self, format):
+ (i,) = struct.unpack_from(format, self.input, self.read_cursor)
+ self.read_cursor += struct.calcsize(format)
+ return i
+
+ def _write_num(self, format, num):
+ s = struct.pack(format, num)
+ self.write(s)
--- /dev/null
+Copyright (c) 2010 Gavin Andresen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+Most of this information is now at the Protocol Specification page at the bitcoin wiki:
+ https://en.bitcoin.it/wiki/Protocol_specification
+
+One of these days I might check this document against the wiki, add any missing information to the wiki, and remove the information below.
+
+
+Transaction dissection notes:
+-----------------------------
+
+My first two transactions look like this:
+
+==WalletTransaction== f240...2582
+Merkle hashBlock: 6d40...0000
+1 tx in, 1 out
+['TxIn: prev(e9d4...d2f4:0) pubkey: 1Lfic7L3z7xUbxRDvMNFiE3QxKRmSSrvEH sig: 72:3045...1a01 65:0403...7b86']
+['TxOut: value: 500.00 pubkey: 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 Script: DUP HASH160 20:61e4...614c EQUALVERIFY CHECKSIG']
+ timeReceived:Thu May 27 14:25:48 2010 fromMe:True spent:True
+
+==WalletTransaction== f961...4472
+Merkle hashBlock: 7707...0000
+1 tx in, 2 out
+['TxIn: prev(f240...2582:0) pubkey: 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 sig: 72:3045...a801 65:04a6...a541']
+['TxOut: value: 142.40 pubkey: 15F9miF9egehbeCiPKVSJSwgw5J96my7Rf Script: DUP HASH160 20:2e8d...5179 EQUALVERIFY CHECKSIG', 'TxOut: value: 357.60 pubkey: 1Av6tJSFv3bZvUfaALm4TQZmYvdeuHcbUb Script: DUP HASH160 20:6cc4...1755 EQUALVERIFY CHECKSIG']
+ timeReceived:Fri May 28 13:55:23 2010 fromMe:True spent:True
+
+So: whoever has sending bc address 1Lfic7L3z7xUbxRDvMNFiE3QxKRmSSrvEH sent 500 bitcoins to
+my 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 receiving address.
+
+I then sent 142.40 to bcaddress 15F9miF9egehbeCiPKVSJSwgw5J96my7Rf.
+And got 357.60 in change (Bitcoin created a new address for the change: 1Av6tJSFv3bZvUfaALm4TQZmYvdeuHcbUb)
+
+Spending generated coins looks different:
+
+Transaction 6403...cc0e
+['TxIn: COIN GENERATED coinbase:0464ba0e1c010a']
+['TxOut: value: 50.00 pubkey: 1Cpr8WJYa5igdo8AtPS24hfVLkk6ANZSWN Script: 65:0442...11c2 CHECKSIG']
+
+Transaction db39...8dcd:
+['TxIn: COIN GENERATED coinbase:0464ba0e1c0130']
+['TxOut: value: 50.00 pubkey: 1LuamEE1rL1QAdgn48gGFD9T9mnqSeP17w Script: 65:04f2...e10f CHECKSIG']
+
+==WalletTransaction== 3860...f888
+['TxIn: prev(6403...cc0e:0) pubkey: (None) sig: 72:3045...f501', 'TxIn: prev(db39...8dcd:0) pubkey: (None) sig: 73:3046...9\
+601']
+['TxOut: value: 100.00 pubkey: 1BBWNxMqU2bTwSVeUDVKmNYJrWr4D1pBrf Script: DUP HASH160 20:6fad...ab90 EQUALVERIFY CHECKSIG']
+
+Here I received 100 bitcoins from two 50 COIN GENERATED transactions.
+
+
+blkindex.dat serialization notes:
+---------------------------------
+
+Keys beging with a serialized string, rest of key and value depend on that first string. Possibilities are:
+
+tx : key continues with uint256 transaction hash/id (32 bytes)
+ value is:
+ version : 32-bit unsigned int
+ CDiskTxPos : location of transaction (in the blk000N.dat files)
+ vector<CDiskTxPos> : location of transactions that spend this transaction's inputs
+
+blockindex : key continues with uint256 block hash (32 bytes)
+ value is serialized CDiskBlockIndex
+
+version : value is integer version number
+
+hashBestChain : value is 32-byte SHA256 hash of last block in the longest block chain.
+
+
+wallet.dat serialization notes:
+-------------------------------
+Berkely DB BTREE file (key-value pairs).
+
+Keys begin with a serialized string, rest of key and value depend on that first string. Possibilities are:
+
+name : key continues with 34-character Bitcoin "hash160" address
+ value is a simple string.
+
+tx : key continues with uint256 (32 bytes)
+ value is a serialized CTransaction+CMerkleTx+CWalletTx (see below for details)
+
+key : key continues with vector<unsigned char> that is public key
+ value is vector<unsigned char> that is private key (full 271-byte
+ OpenSSL representation)
+
+wkey : key continues with vector<unsigned char> that is public key
+ value is serialized CWalletKey:
+ vector<unsigned char> that is private key
+ int64 nTimeCreated
+ int64 nTimeExpires
+ string comment
+
+ckey : enCrypted key continues with vector<unsigned char> that is public key
+ value is vector<unsigned char> that is private key (32 byte private part,
+ not full 271-byte OpenSSL representation)
+
+mkey : master key continues with unsigned int that is ID of the master key
+ value is serialized CMasterKey:
+ vector<unsigned char> encrypted key
+ vector<unsigned char> encrypted salt
+ unsigned int indicating the method to derive key from password
+ unsigned int indicating the number of times the derivation algorithm should be iterated
+ vector<unsigned char> holding other parameters to the derivation algorithm
+
+defaultkey :
+ value is vector<unsigned char> default key
+
+version :
+ value is int version number
+
+setting : key continues with setting name, value depends on setting as follows:
+ addrIncoming : CAddress serialized
+ addrProxy : CAddress serialized
+ fGenerateBitcoins : 1-character (boolean)
+ fLimitProcessors : 1-character (boolean)
+ fMinimizeOnClose : 1-character (boolean)
+ fMinimizeToTray : 1-character (boolean)
+ fUseProxy : 1-character (boolean)
+ nLimitProcessors : int
+ nTransactionFee : int64
+
+
+Complex value type serialization:
+---------------------------------
+
+CDiskTxPos:
+ uint32 nFile : Which of the blk000*.dat files
+ uint32 nBlockPos : Byte position in file
+ uint32 nTxPos : Byte position in file
+
+CDiskBlockIndex:
+ int nVersion
+ uint256 hashNext
+ uint32 nFile : Which of the blk000*.dat files
+ uint32 nBlockPos : Byte position in file
+ int nHeight : height in block chain
+ int blockVersion : ??? Not sure why version is repeated...
+ uint256 hashPrev
+ uint256 hashMerkleRoot
+ uint32 nTime
+ uint32 nBits
+ uint32 nNonce
+
+CBlock:
+ # Note: 4 bytes: f9 be b4 d9 are written before records in blk000N.dat files
+ # But the nBlockPos pointers in CDiskBlockIndex points to start of serializedSize
+ int serializedSize
+ int version
+ uint256 hashPrev
+ uint256 hashMerkleRoot
+ uint32 nTime
+ uint32 nBits
+ uint32 nNonce
+ vector<CTransaction>
+
+CAddress:
+ int nVersion
+ unsigned int nTime
+ uint64 nServices
+ unsigned char[12] pchReserved
+ unsigned int ip
+ unsigned short port
+
+CTransaction:
+ int nVersion
+ vector<CTxIn> vin
+ vector<CTxOut> vout
+ unsigned int nLockTime
+
+CTxIn:
+ COutPoint prevout
+ CScript scriptSig
+ unsigned int nSequence
+
+CTxOut:
+ int64 nValue
+ CScript scriptPubKey
+
+COutPoint:
+ 36 bytes(FLATDATA): 32-byte hash, 4-byte unsigned int
+
+CScript:
+ vector<unsigned char> containing built-in little scripting-language script
+ (opcodes and arguments to do crypto stuff)
+
+CMerkleTx:
+ ... serialize CTransaction, then:
+ uint256 hashBlock
+ vector<uint256> vMerkleBranch
+ int nIndex
+
+CWalletTx:
+ ... serialized CMerkleTx, then:
+ vector<CMerkleTx> vtxPrev
+ map<string,string> mapValue
+ vector<pair<string,string> > vOrderForm
+ unsigned int fTimeReceivedIsTxTime
+ unsigned int nTimeReceived
+ char fFromMe
+ char fSpent
+
+CInv:
+ int type : 1:tx 2:block
+ uint256 hash
+
+CBlockLocator:
+ int nVersion
+ vector<uint256> # Block hashes, newest back to genesis block (dense to start, but then sparse)
+
+string:
+ 1/3/5/9 bytes giving length:
+ 1 byte if length < 253.
+ otherwise byte value '253'+ushort, '254'+uint, '255'+uint64
+ then length bytes.
+
+vector<type>:
+ 1/3/5/9 bytes giving count (see string, above)
+ followed by that many <types> serialized one-after-another
+
+pair:
+ just first item followed by second item
+
+
+PUBLIC KEYS TO BITCOIN ADDRESSES
+--------------------------------
+
+Public key, in memory (65 bytes):
+
+0x94c7818: 0x04 0x57 0xcc 0xad 0xd7 0x1e 0xb0 0xf3
+0x94c7820: 0xc1 0x9d 0x22 0xb9 0xba 0x0e 0xa1 0xf3
+0x94c7828: 0x44 0x2a 0x6f 0x12 0x31 0x46 0xb5 0xbd
+0x94c7830: 0xff 0x10 0x60 0xbc 0xd1 0x11 0x68 0xe6
+0x94c7838: 0x6a 0x71 0xbe 0xd4 0xda 0x17 0x7c 0x12
+0x94c7840: 0xd7 0x30 0x9a 0xdd 0xfd 0xf5 0x6c 0x31
+0x94c7848: 0xd5 0xc8 0xa2 0x7b 0x8e 0x6a 0x22 0x20
+0x94c7850: 0x38 0x42 0xc6 0xc2 0x4f 0xd5 0x9b 0xd7
+0x94c7858: 0xb7
+Python string:
+public_key = "\x04\x57\xcc\xad\xd7\x1e\xb0\xf3\xc1\x9d\x22\xb9\xba\x0e\xa1\xf3\x44\x2a\x6f\x12\x31\x46\xb5\xbd\xff\x10\x60\xbc\xd1\x11\x68\xe6\x6a\x71\xbe\xd4\xda\x17\x7c\x12\xd7\x30\x9a\xdd\xfd\xf5\x6c\x31\xd5\xc8\xa2\x7b\x8e\x6a\x22\x20\x38\x42\xc6\xc2\x4f\xd5\x9b\xd7\xb7"
+
+SHA256 hash of that is:
+0xb6890938: 0x0d 0x72 0xab 0x02 0xc8 0xab 0x52 0xce
+0xb6890940: 0x7e 0x6b 0x04 0x00 0x95 0x58 0x09 0xf0
+0xb6890948: 0x93 0x48 0x21 0xb6 0x26 0xc3 0x27 0xc7
+0xb6890950: 0x9a 0x07 0x62 0xfd 0xbc 0x5e 0xb8 0xa5
+h1 = SHA256.new(public_key).digest()
+
+RIPEMD160(SHA256(public key)) is:
+0xb68909ac: 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 0xa2
+0xb68909b4: 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 0x67
+0xb68909bc: 0x28 0x26 0x30 0xd3
+h2 = RIPEMD160.new(h1).digest()
+
+Put version number '0x00' byte onto front:
+0x9eeb840: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3
+0x9eeb848: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8
+0x9eeb850: 0x67 0x28 0x26 0x30 0xd3
+vh2 = "\x00"+h2
+
+Hash (double-SHA256) that:
+0xb68908e8: 0xf9 0xb7 0x8e 0x64 0x6e 0x20 0x27 0xc4
+0xb68908f0: 0xaa 0x62 0x66 0x04 0x2e 0xb6 0xa2 0xe0
+0xb68908f8: 0x41 0x03 0x9d 0xd8 0xe2 0x24 0x24 0xe8
+0xb6890900: 0x50 0xac 0x20 0x29 0xfb 0xcd 0xb4 0x6e
+h3=SHA256.new(SHA256.new(vh2).digest()).digest()
+
+Add first 4 bytes from Hash object onto end as check-bytes:
+0x9fa6628: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3
+0x9fa6630: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8
+0x9fa6638: 0x67 0x28 0x26 0x30 0xd3 0xf9 0xb7 0x8e
+0x9fa6640: 0x64
+addr=vh2+h3[0:4]
+
+Result length should be: int(math.floor(len(addr)*8/math.log(58,2)))
+Base58 encode that, front-pad with '1's if less than expected length
+to get: 19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT
+
+
+WIRE PROTOCOL NOTES
+-------------------
+Default port is 8333
+
+// Message format
+// (4) message start { 0xf9, 0xbe, 0xb4, 0xd9 }
+// (12) command
+// (4) size -- number of bytes of data
+// (4) checksum -- First four bytes of double SHA256 hash of data
+// (x) data
+
+ --> messages might be split by network layer...
+
+Commands are:
+
+"version" :
+ int nVersion
+ uint64 nServices
+ int64 nTime
+ CAddress addrMe
+ CAddress addrFrom # if nVersion > 106
+ uint64 nNonce
+ string strSubVer # if nVersion > 106
+ int nStartingHeight # if nVersion > 209
+
+nNonce is random value (I think), used to detect self-connection.
+
+"verack" : no data. Sent to sync min version (peer, peer)
+
+"addr" :
+ vector<CAddress>
+ (relayed to 10 random nodes so they spread)
+
+"inv" :
+ vector<CInv>
+
+"getdata" : Asks for blocks or tx's (response is "block" or "tx" messages)
+ vector<CInv>
+
+"getblocks" :
+ CBLockLocator
+ uint256 hashStop
+
+"tx" :
+ CTransaction
+
+"block" :
+ CBlock
+
+"getaddr" : no data ("please send me addr messages")
+
+"checkorder"
+ uint256 hashReply
+ CWalletTx order
+
+"submitorder"
+ uint256 hashReply
+ CWalletTx wtxNew
+
+"reply"
+ uint256 hashReply
+
+"ping" : no data (and no reply)
+
+
+TRANSACTION SIGNING NOTES
+-------------------------
+
+So, I want to spend some bitcoin.
+First, I gotta have a Transaction to me-- specifically, a Transaction with a TxOut that contains a scriptPubKey that I can satisfy.
+
+TxOut.scriptPubKey's come in a couple different flavors:
+ DUP HASH160 {hash160(public_key) EQUALVERIFY CHECKSIG
+ --> I have to be able to supply a matching TxIn.scriptSig with: signature and public key
+ (public key) CHECKSIG
+ --> I have to supply a matching TxIn.scriptSig with: signature
+
+TODO: figure out what, exactly, is hashed (and how), and how, exactly, that hash value is signed with the private key.
+
+DIFFICULTY, NBITS, BN_mpi2bn
+----------------------------
+
+Hash targets are stored as "compact bignums;" the production block chain has an initial target of:
+0x1d00ffff ... which means "create a bignum that is 0x1d (29) bytes long and has 0x00ffff as the
+three bytes on the big end." Or: 0xffff0000000000000000000000000000000000000000000000000000
+
+As I write this, the current block->nBits is 0x1c010c5. To compute difficulty:
+ php -r '$nBits = 0x1c010c5a; print( (0xffff << ((0x1d-($nBits>>24))*8)) / ($nBits&0xffffff) );'
+
+
--- /dev/null
+----- dbdump.py -----
+Run dbdump.py --help for usage. Database files are opened read-only, but
+you might want to backup your Bitcoin wallet.dat file just in case.
+
+You must quit Bitcoin before reading the transactions, blocks, or address database files.
+
+Requires the pycrypto library from http://www.dlitz.net/software/pycrypto/
+to translate public keys into human-friendly Bitcoin addresses.
+
+Examples:
+
+Print out wallet keys and transactions:
+ dbdump.py --wallet --wallet-tx
+
+Print out the "genesis block" (the very first block in the proof-of-work block chain):
+ dbdump.py --block=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
+
+Print out one of the transactions from my wallet:
+ dbdump.py --transaction=c6e1bf883bceef0aa05113e189982055d9ba7212ddfc879798616a0d0828c98c
+ dbdump.py --transaction=c6e1...c98c
+
+Print out all 'received' transactions that aren't yet spent:
+ dbdump.py --wallet-tx-filter='fromMe:False.*spent:False'
+
+Print out all blocks involving transactions to the Bitcoin Faucet:
+ dbdump.py --search-blocks=15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC
+
+There's a special search term to look for non-standard transactions:
+ dbdump.py --search-blocks=NONSTANDARD_CSCRIPTS
+
+----- statistics.py -----
+Scan all the transactions in the block chain and dump out a .csv file that shows transaction volume per month.
+
+----- fixwallet.py -----
+Half-baked utility that reads a wallet.dat and writes out a new wallet.dat.
+
+Only half-baked because to be really useful I'd have to write serialize routines to re-pack data after modifying it...
+
+----- jsonToCSV.py -----
+Read JSON list-of-objects from standard input, writes CSV file to standard output.
+Useful for converting bitcoind's listtransactions output to CSV that can be
+imported into a spreadsheet.
--- /dev/null
+#
+# Code for parsing the addr.dat file
+# NOTE: I think you have to shutdown the Bitcoin client to
+# successfully read addr.dat...
+#
+
+from bsddb.db import *
+import logging
+from operator import itemgetter
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address
+from util import short_hex
+from deserialize import *
+
+def dump_addresses(db_env):
+ db = DB(db_env)
+ try:
+ r = db.open("addr.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open addr.dat/main. Try quitting Bitcoin and running this again.")
+ sys.exit(1)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ for (key, value) in db.items():
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+
+ if type == "addr":
+ d = parse_CAddress(vds)
+ print(deserialize_CAddress(d))
+
+ db.close()
--- /dev/null
+#!/usr/bin/env python
+
+"""encode/decode base58 in the same way that Bitcoin does"""
+
+import math
+
+__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+__b58base = len(__b58chars)
+
+def b58encode(v):
+ """ encode v, which is a string of bytes, to base58.
+ """
+
+ long_value = 0L
+ for (i, c) in enumerate(v[::-1]):
+ long_value += (256**i) * ord(c)
+
+ result = ''
+ while long_value >= __b58base:
+ div, mod = divmod(long_value, __b58base)
+ result = __b58chars[mod] + result
+ long_value = div
+ result = __b58chars[long_value] + result
+
+ # Bitcoin does a little leading-zero-compression:
+ # leading 0-bytes in the input become leading-1s
+ nPad = 0
+ for c in v:
+ if c == '\0': nPad += 1
+ else: break
+
+ return (__b58chars[0]*nPad) + result
+
+def b58decode(v, length):
+ """ decode v into a string of len bytes
+ """
+ long_value = 0L
+ for (i, c) in enumerate(v[::-1]):
+ long_value += __b58chars.find(c) * (__b58base**i)
+
+ result = ''
+ while long_value >= 256:
+ div, mod = divmod(long_value, 256)
+ result = chr(mod) + result
+ long_value = div
+ result = chr(long_value) + result
+
+ nPad = 0
+ for c in v:
+ if c == __b58chars[0]: nPad += 1
+ else: break
+
+ result = chr(0)*nPad + result
+ if length is not None and len(result) != length:
+ return None
+
+ return result
+
+try:
+ # Python Crypto library is at: http://www.dlitz.net/software/pycrypto/
+ # Needed for RIPEMD160 hash function, used to compute
+ # Bitcoin addresses from internal public keys.
+ import Crypto.Hash.SHA256 as SHA256
+ import Crypto.Hash.RIPEMD160 as RIPEMD160
+ have_crypto = True
+except ImportError:
+ have_crypto = False
+
+def hash_160(public_key):
+ if not have_crypto:
+ return ''
+ h1 = SHA256.new(public_key).digest()
+ h2 = RIPEMD160.new(h1).digest()
+ return h2
+
+def public_key_to_bc_address(public_key):
+ if not have_crypto:
+ return ''
+ h160 = hash_160(public_key)
+ return hash_160_to_bc_address(h160)
+
+def hash_160_to_bc_address(h160):
+ if not have_crypto:
+ return ''
+ vh160 = "\x00"+h160 # \x00 is version 0
+ h3=SHA256.new(SHA256.new(vh160).digest()).digest()
+ addr=vh160+h3[0:4]
+ return b58encode(addr)
+
+def bc_address_to_hash_160(addr):
+ bytes = b58decode(addr, 25)
+ return bytes[1:21]
+
+if __name__ == '__main__':
+ x = '005cc87f4a3fdfe3a2346b6953267ca867282630d3f9b78e64'.decode('hex_codec')
+ encoded = b58encode(x)
+ print encoded, '19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT'
+ print b58decode(encoded, len(x)).encode('hex_codec'), x.encode('hex_codec')
--- /dev/null
+#
+# Code for parsing the blkindex.dat file
+#
+
+from bsddb.db import *
+import logging
+from operator import itemgetter
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address
+from util import short_hex
+from deserialize import *
+
+def dump_blkindex_summary(db_env):
+ db = DB(db_env)
+ try:
+ r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.")
+ sys.exit(1)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ n_tx = 0
+ n_blockindex = 0
+
+ print("blkindex file summary:")
+ for (key, value) in db.items():
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+
+ if type == "tx":
+ n_tx += 1
+ elif type == "blockindex":
+ n_blockindex += 1
+ elif type == "version":
+ version = vds.read_int32()
+ print(" Version: %d"%(version,))
+ elif type == "hashBestChain":
+ hash = vds.read_bytes(32)
+ print(" HashBestChain: %s"%(hash.encode('hex_codec'),))
+ else:
+ logging.warn("blkindex: unknown type '%s'"%(type,))
+ continue
+
+ print(" %d transactions, %d blocks."%(n_tx, n_blockindex))
+ db.close()
--- /dev/null
+#
+# Code for dumping a single block, given its ID (hash)
+#
+
+from bsddb.db import *
+import logging
+import os.path
+import re
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address
+from util import short_hex, long_hex
+from deserialize import *
+
+def _open_blkindex(db_env):
+ db = DB(db_env)
+ try:
+ r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
+ except DBError:
+ r = True
+ if r is not None:
+ logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.")
+ sys.exit(1)
+ return db
+
+def _read_CDiskTxPos(stream):
+ n_file = stream.read_uint32()
+ n_block_pos = stream.read_uint32()
+ n_tx_pos = stream.read_uint32()
+ return (n_file, n_block_pos, n_tx_pos)
+
+def _dump_block(datadir, nFile, nBlockPos, hash256, hashNext, do_print=True):
+ blockfile = open(os.path.join(datadir, "blk%04d.dat"%(nFile,)), "rb")
+ ds = BCDataStream()
+ ds.map_file(blockfile, nBlockPos)
+ d = parse_Block(ds)
+ block_string = deserialize_Block(d)
+ ds.close_file()
+ blockfile.close()
+ if do_print:
+ print "BLOCK "+long_hex(hash256[::-1])
+ print "Next block: "+long_hex(hashNext[::-1])
+ print block_string
+ return block_string
+
+def _parse_block_index(vds):
+ d = {}
+ d['version'] = vds.read_int32()
+ d['hashNext'] = vds.read_bytes(32)
+ d['nFile'] = vds.read_uint32()
+ d['nBlockPos'] = vds.read_uint32()
+ d['nHeight'] = vds.read_int32()
+
+ header_start = vds.read_cursor
+ d['b_version'] = vds.read_int32()
+ d['hashPrev'] = vds.read_bytes(32)
+ d['hashMerkle'] = vds.read_bytes(32)
+ d['nTime'] = vds.read_int32()
+ d['nBits'] = vds.read_int32()
+ d['nNonce'] = vds.read_int32()
+ header_end = vds.read_cursor
+ d['__header__'] = vds.input[header_start:header_end]
+ return d
+
+def dump_block(datadir, db_env, block_hash):
+ """ Dump a block, given hexadecimal hash-- either the full hash
+ OR a short_hex version of the it.
+ """
+ db = _open_blkindex(db_env)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ n_blockindex = 0
+
+ key_prefix = "\x0ablockindex"
+ cursor = db.cursor()
+ (key, value) = cursor.set_range(key_prefix)
+
+ while key.startswith(key_prefix):
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+ hash256 = kds.read_bytes(32)
+ hash_hex = long_hex(hash256[::-1])
+ block_data = _parse_block_index(vds)
+
+ if (hash_hex.startswith(block_hash) or short_hex(hash256[::-1]).startswith(block_hash)):
+ print "Block height: "+str(block_data['nHeight'])
+ _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], hash256, block_data['hashNext'])
+
+ (key, value) = cursor.next()
+
+ db.close()
+
+def read_block(db_cursor, hash):
+ (key,value) = db_cursor.set_range("\x0ablockindex"+hash)
+ vds = BCDataStream()
+ vds.clear(); vds.write(value)
+ block_data = _parse_block_index(vds)
+ block_data['hash256'] = hash
+ return block_data
+
+def scan_blocks(datadir, db_env, callback_fn):
+ """ Scan through blocks, from last through genesis block,
+ calling callback_fn(block_data) for each.
+ callback_fn should return False if scanning should
+ stop, True if it should continue.
+ Returns last block_data scanned.
+ """
+ db = _open_blkindex(db_env)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ # Read the hashBestChain record:
+ cursor = db.cursor()
+ (key, value) = cursor.set_range("\x0dhashBestChain")
+ vds.write(value)
+ hashBestChain = vds.read_bytes(32)
+
+ block_data = read_block(cursor, hashBestChain)
+
+ while callback_fn(block_data):
+ if block_data['nHeight'] == 0:
+ break;
+ block_data = read_block(cursor, block_data['hashPrev'])
+ return block_data
+
+
+def dump_block_n(datadir, db_env, block_number):
+ """ Dump a block given block number (== height, genesis block is 0)
+ """
+ def scan_callback(block_data):
+ return not block_data['nHeight'] == block_number
+
+ block_data = scan_blocks(datadir, db_env, scan_callback)
+
+ print "Block height: "+str(block_data['nHeight'])
+ _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], block_data['hash256'], block_data['hashNext'])
+
+def search_blocks(datadir, db_env, pattern):
+ """ Dump a block given block number (== height, genesis block is 0)
+ """
+ db = _open_blkindex(db_env)
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ # Read the hashBestChain record:
+ cursor = db.cursor()
+ (key, value) = cursor.set_range("\x0dhashBestChain")
+ vds.write(value)
+ hashBestChain = vds.read_bytes(32)
+ block_data = read_block(cursor, hashBestChain)
+
+ if pattern == "NONSTANDARD_CSCRIPTS": # Hack to look for non-standard transactions
+ search_odd_scripts(datadir, cursor, block_data)
+ return
+
+ while True:
+ block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'],
+ block_data['hash256'], block_data['hashNext'], False)
+
+ if re.search(pattern, block_string) is not None:
+ print "MATCH: Block height: "+str(block_data['nHeight'])
+ print block_string
+
+ if block_data['nHeight'] == 0:
+ break
+ block_data = read_block(cursor, block_data['hashPrev'])
+
+def search_odd_scripts(datadir, cursor, block_data):
+ """ Look for non-standard transactions """
+ while True:
+ block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'],
+ block_data['hash256'], block_data['hashNext'], False)
+
+ found_nonstandard = False
+ for m in re.finditer(r'TxIn:(.*?)$', block_string, re.MULTILINE):
+ s = m.group(1)
+ if re.match(r'\s*COIN GENERATED coinbase:\w+$', s): continue
+ if re.match(r'.*sig: \d+:\w+...\w+ \d+:\w+...\w+$', s): continue
+ if re.match(r'.*sig: \d+:\w+...\w+$', s): continue
+ print "Nonstandard TxIn: "+s
+ found_nonstandard = True
+ break
+
+ for m in re.finditer(r'TxOut:(.*?)$', block_string, re.MULTILINE):
+ s = m.group(1)
+ if re.match(r'.*Script: DUP HASH160 \d+:\w+...\w+ EQUALVERIFY CHECKSIG$', s): continue
+ if re.match(r'.*Script: \d+:\w+...\w+ CHECKSIG$', s): continue
+ print "Nonstandard TxOut: "+s
+ found_nonstandard = True
+ break
+
+ if found_nonstandard:
+ print "NONSTANDARD TXN: Block height: "+str(block_data['nHeight'])
+ print block_string
+
+ if block_data['nHeight'] == 0:
+ break
+ block_data = read_block(cursor, block_data['hashPrev'])
+
+def check_block_chain(db_env):
+ """ Make sure hashPrev/hashNext pointers are consistent through block chain """
+ db = _open_blkindex(db_env)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ # Read the hashBestChain record:
+ cursor = db.cursor()
+ (key, value) = cursor.set_range("\x0dhashBestChain")
+ vds.write(value)
+ hashBestChain = vds.read_bytes(32)
+
+ back_blocks = []
+
+ block_data = read_block(cursor, hashBestChain)
+
+ while block_data['nHeight'] > 0:
+ back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) )
+ block_data = read_block(cursor, block_data['hashPrev'])
+
+ back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) )
+ genesis_block = block_data
+
+ print("check block chain: genesis block merkle hash is: %s"%(block_data['hashMerkle'][::-1].encode('hex_codec')))
+
+ while block_data['hashNext'] != ('\0'*32):
+ forward = (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext'])
+ back = back_blocks.pop()
+ if forward != back:
+ print("Forward/back block mismatch at height %d!"%(block_data['nHeight'],))
+ print(" Forward: "+str(forward))
+ print(" Back: "+str(back))
+ block_data = read_block(cursor, block_data['hashNext'])
--- /dev/null
+#
+# Code for parsing the blkindex.dat file
+#
+
+from bsddb.db import *
+import logging
+from operator import itemgetter
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address
+from util import short_hex
+from deserialize import *
+
+def _read_CDiskTxPos(stream):
+ n_file = stream.read_uint32()
+ n_block_pos = stream.read_uint32()
+ n_tx_pos = stream.read_uint32()
+ return (n_file, n_block_pos, n_tx_pos)
+
+def dump_blockindex(db_env, owner=None, n_to_dump=1000):
+ db = DB(db_env)
+ r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
+ if r is not None:
+ logging.error("Couldn't open blkindex.dat/main")
+ sys.exit(1)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ wallet_transactions = []
+
+ for (i, (key, value)) in enumerate(db.items()):
+ if i > n_to_dump:
+ break
+
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+
+ if type == "tx":
+ hash256 = kds.read_bytes(32)
+ version = vds.read_uint32()
+ tx_pos = _read_CDiskTxPos(vds)
+ print("Tx(%s:%d %d %d)"%((short_hex(hash256),)+tx_pos))
+ n_tx_out = vds.read_compact_size()
+ for i in range(0,n_tx_out):
+ tx_out = _read_CDiskTxPos(vds)
+ if tx_out[0] != 0xffffffffL: # UINT_MAX means no TxOuts (unspent)
+ print(" ==> TxOut(%d %d %d)"%tx_out)
+
+ else:
+ logging.warn("blkindex: type %s"%(type,))
+ continue
+
+ db.close()
+
--- /dev/null
+#!/usr/bin/env python
+#
+# Code for dumping the bitcoin Berkeley db files in a human-readable format
+#
+from bsddb.db import *
+import logging
+import sys
+
+from address import dump_addresses
+from wallet import dump_wallet, dump_accounts
+from blkindex import dump_blkindex_summary
+from transaction import dump_transaction
+from block import dump_block, dump_block_n, search_blocks, check_block_chain
+from util import determine_db_dir, create_env
+
+def main():
+ import optparse
+ parser = optparse.OptionParser(usage="%prog [options]")
+ parser.add_option("--datadir", dest="datadir", default=None,
+ help="Look for files here (defaults to bitcoin default)")
+ parser.add_option("--wallet", action="store_true", dest="dump_wallet", default=False,
+ help="Print out contents of the wallet.dat file")
+ parser.add_option("--wallet-tx", action="store_true", dest="dump_wallet_tx", default=False,
+ help="Print transactions in the wallet.dat file")
+ parser.add_option("--wallet-tx-filter", action="store", dest="wallet_tx_filter", default="",
+ help="Only print transactions that match given string/regular expression")
+ parser.add_option("--accounts", action="store_true", dest="dump_accounts", default="",
+ help="Print out account names, one per line")
+ parser.add_option("--blkindex", action="store_true", dest="dump_blkindex", default=False,
+ help="Print out summary of blkindex.dat file")
+ parser.add_option("--check-block-chain", action="store_true", dest="check_chain", default=False,
+ help="Scan back and forward through the block chain, looking for inconsistencies")
+ parser.add_option("--address", action="store_true", dest="dump_addr", default=False,
+ help="Print addresses in the addr.dat file")
+ parser.add_option("--transaction", action="store", dest="dump_transaction", default=None,
+ help="Dump a single transaction, given hex transaction id (or abbreviated id)")
+ parser.add_option("--block", action="store", dest="dump_block", default=None,
+ help="Dump a single block, given its hex hash (or abbreviated hex hash) OR block height")
+ parser.add_option("--search-blocks", action="store", dest="search_blocks", default=None,
+ help="Search the block chain for blocks containing given regex pattern")
+ (options, args) = parser.parse_args()
+
+ if options.datadir is None:
+ db_dir = determine_db_dir()
+ else:
+ db_dir = options.datadir
+
+ try:
+ db_env = create_env(db_dir)
+ except DBNoSuchFileError:
+ logging.error("Couldn't open " + db_dir)
+ sys.exit(1)
+
+ dump_tx = options.dump_wallet_tx
+ if len(options.wallet_tx_filter) > 0:
+ dump_tx = True
+ if options.dump_wallet or dump_tx:
+ dump_wallet(db_env, options.dump_wallet, dump_tx, options.wallet_tx_filter)
+ if options.dump_accounts:
+ dump_accounts(db_env)
+
+ if options.dump_addr:
+ dump_addresses(db_env)
+
+ if options.check_chain:
+ check_block_chain(db_env)
+
+ if options.dump_blkindex:
+ dump_blkindex_summary(db_env)
+
+ if options.dump_transaction is not None:
+ dump_transaction(db_dir, db_env, options.dump_transaction)
+
+ if options.dump_block is not None:
+ if len(options.dump_block) < 7: # Probably an integer...
+ try:
+ dump_block_n(db_dir, db_env, int(options.dump_block))
+ except ValueError:
+ dump_block(db_dir, db_env, options.dump_block)
+ else:
+ dump_block(db_dir, db_env, options.dump_block)
+
+ if options.search_blocks is not None:
+ search_blocks(db_dir, db_env, options.search_blocks)
+
+ db_env.close()
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#
+#
+#
+
+from BCDataStream import *
+from enumeration import Enumeration
+from base58 import public_key_to_bc_address, hash_160_to_bc_address
+import socket
+import time
+from util import short_hex, long_hex
+
+def parse_CAddress(vds):
+ d = {}
+ d['nVersion'] = vds.read_int32()
+ d['nTime'] = vds.read_uint32()
+ d['nServices'] = vds.read_uint64()
+ d['pchReserved'] = vds.read_bytes(12)
+ d['ip'] = socket.inet_ntoa(vds.read_bytes(4))
+ d['port'] = vds.read_uint16()
+ return d
+
+def deserialize_CAddress(d):
+ return d['ip']+":"+str(d['port'])+" (lastseen: %s)"%(time.ctime(d['nTime']),)
+
+def parse_setting(setting, vds):
+ if setting[0] == "f": # flag (boolean) settings
+ return str(vds.read_boolean())
+ elif setting == "addrIncoming":
+ return "" # bitcoin 0.4 purposely breaks addrIncoming setting in encrypted wallets.
+ elif setting[0:4] == "addr": # CAddress
+ d = parse_CAddress(vds)
+ return deserialize_CAddress(d)
+ elif setting == "nTransactionFee":
+ return vds.read_int64()
+ elif setting == "nLimitProcessors":
+ return vds.read_int32()
+ return 'unknown setting'
+
+def parse_TxIn(vds):
+ d = {}
+ d['prevout_hash'] = vds.read_bytes(32)
+ d['prevout_n'] = vds.read_uint32()
+ d['scriptSig'] = vds.read_bytes(vds.read_compact_size())
+ d['sequence'] = vds.read_uint32()
+ return d
+def deserialize_TxIn(d, transaction_index=None, owner_keys=None):
+ if d['prevout_hash'] == "\x00"*32:
+ result = "TxIn: COIN GENERATED"
+ result += " coinbase:"+d['scriptSig'].encode('hex_codec')
+ elif transaction_index is not None and d['prevout_hash'] in transaction_index:
+ p = transaction_index[d['prevout_hash']]['txOut'][d['prevout_n']]
+ result = "TxIn: value: %f"%(p['value']/1.0e8,)
+ result += " prev("+long_hex(d['prevout_hash'][::-1])+":"+str(d['prevout_n'])+")"
+ else:
+ result = "TxIn: prev("+long_hex(d['prevout_hash'][::-1])+":"+str(d['prevout_n'])+")"
+ pk = extract_public_key(d['scriptSig'])
+ result += " pubkey: "+pk
+ result += " sig: "+decode_script(d['scriptSig'])
+ if d['sequence'] < 0xffffffff: result += " sequence: "+hex(d['sequence'])
+ return result
+
+def parse_TxOut(vds):
+ d = {}
+ d['value'] = vds.read_int64()
+ d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size())
+ return d
+
+def deserialize_TxOut(d, owner_keys=None):
+ result = "TxOut: value: %f"%(d['value']/1.0e8,)
+ pk = extract_public_key(d['scriptPubKey'])
+ result += " pubkey: "+pk
+ result += " Script: "+decode_script(d['scriptPubKey'])
+ if owner_keys is not None:
+ if pk in owner_keys: result += " Own: True"
+ else: result += " Own: False"
+ return result
+
+def parse_Transaction(vds):
+ d = {}
+ d['version'] = vds.read_int32()
+ n_vin = vds.read_compact_size()
+ d['txIn'] = []
+ for i in xrange(n_vin):
+ d['txIn'].append(parse_TxIn(vds))
+ n_vout = vds.read_compact_size()
+ d['txOut'] = []
+ for i in xrange(n_vout):
+ d['txOut'].append(parse_TxOut(vds))
+ d['lockTime'] = vds.read_uint32()
+ return d
+def deserialize_Transaction(d, transaction_index=None, owner_keys=None):
+ result = "%d tx in, %d out\n"%(len(d['txIn']), len(d['txOut']))
+ for txIn in d['txIn']:
+ result += deserialize_TxIn(txIn, transaction_index) + "\n"
+ for txOut in d['txOut']:
+ result += deserialize_TxOut(txOut, owner_keys) + "\n"
+ return result
+
+def parse_MerkleTx(vds):
+ d = parse_Transaction(vds)
+ d['hashBlock'] = vds.read_bytes(32)
+ n_merkleBranch = vds.read_compact_size()
+ d['merkleBranch'] = vds.read_bytes(32*n_merkleBranch)
+ d['nIndex'] = vds.read_int32()
+ return d
+
+def deserialize_MerkleTx(d, transaction_index=None, owner_keys=None):
+ tx = deserialize_Transaction(d, transaction_index, owner_keys)
+ result = "block: "+(d['hashBlock'][::-1]).encode('hex_codec')
+ result += " %d hashes in merkle branch\n"%(len(d['merkleBranch'])/32,)
+ return result+tx
+
+def parse_WalletTx(vds):
+ d = parse_MerkleTx(vds)
+ n_vtxPrev = vds.read_compact_size()
+ d['vtxPrev'] = []
+ for i in xrange(n_vtxPrev):
+ d['vtxPrev'].append(parse_MerkleTx(vds))
+
+ d['mapValue'] = {}
+ n_mapValue = vds.read_compact_size()
+ for i in xrange(n_mapValue):
+ key = vds.read_string()
+ value = vds.read_string()
+ d['mapValue'][key] = value
+ n_orderForm = vds.read_compact_size()
+ d['orderForm'] = []
+ for i in xrange(n_orderForm):
+ first = vds.read_string()
+ second = vds.read_string()
+ d['orderForm'].append( (first, second) )
+ d['fTimeReceivedIsTxTime'] = vds.read_uint32()
+ d['timeReceived'] = vds.read_uint32()
+ d['fromMe'] = vds.read_boolean()
+ d['spent'] = vds.read_boolean()
+
+ return d
+
+def deserialize_WalletTx(d, transaction_index=None, owner_keys=None):
+ result = deserialize_MerkleTx(d, transaction_index, owner_keys)
+ result += "%d vtxPrev txns\n"%(len(d['vtxPrev']),)
+ result += "mapValue:"+str(d['mapValue'])
+ if len(d['orderForm']) > 0:
+ result += "\n"+" orderForm:"+str(d['orderForm'])
+ result += "\n"+"timeReceived:"+time.ctime(d['timeReceived'])
+ result += " fromMe:"+str(d['fromMe'])+" spent:"+str(d['spent'])
+ return result
+
+# The CAuxPow (auxiliary proof of work) structure supports merged mining.
+# A flag in the block version field indicates the structure's presence.
+# As of 8/2011, the Original Bitcoin Client does not use it. CAuxPow
+# originated in Namecoin; see
+# https://github.com/vinced/namecoin/blob/mergedmine/doc/README_merged-mining.md.
+def parse_AuxPow(vds):
+ d = parse_MerkleTx(vds)
+ n_chainMerkleBranch = vds.read_compact_size()
+ d['chainMerkleBranch'] = vds.read_bytes(32*n_chainMerkleBranch)
+ d['chainIndex'] = vds.read_int32()
+ d['parentBlock'] = parse_BlockHeader(vds)
+ return d
+
+def parse_BlockHeader(vds):
+ d = {}
+ header_start = vds.read_cursor
+ d['version'] = vds.read_int32()
+ d['hashPrev'] = vds.read_bytes(32)
+ d['hashMerkleRoot'] = vds.read_bytes(32)
+ d['nTime'] = vds.read_uint32()
+ d['nBits'] = vds.read_uint32()
+ d['nNonce'] = vds.read_uint32()
+ header_end = vds.read_cursor
+ d['__header__'] = vds.input[header_start:header_end]
+ return d
+
+def parse_Block(vds):
+ d = parse_BlockHeader(vds)
+ if d['version'] & (1 << 8):
+ d['auxpow'] = parse_AuxPow(vds)
+ d['transactions'] = []
+ nTransactions = vds.read_compact_size()
+ for i in xrange(nTransactions):
+ d['transactions'].append(parse_Transaction(vds))
+
+ return d
+
+def deserialize_Block(d):
+ result = "Time: "+time.ctime(d['nTime'])+" Nonce: "+str(d['nNonce'])
+ result += "\nnBits: 0x"+hex(d['nBits'])
+ result += "\nhashMerkleRoot: 0x"+d['hashMerkleRoot'][::-1].encode('hex_codec')
+ result += "\nPrevious block: "+d['hashPrev'][::-1].encode('hex_codec')
+ result += "\n%d transactions:\n"%len(d['transactions'])
+ for t in d['transactions']:
+ result += deserialize_Transaction(t)+"\n"
+ result += "\nRaw block header: "+d['__header__'].encode('hex_codec')
+ return result
+
+def parse_BlockLocator(vds):
+ d = { 'hashes' : [] }
+ nHashes = vds.read_compact_size()
+ for i in xrange(nHashes):
+ d['hashes'].append(vds.read_bytes(32))
+ return d
+
+def deserialize_BlockLocator(d):
+ result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
+ return result
+
+opcodes = Enumeration("Opcodes", [
+ ("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED",
+ "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7",
+ "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16",
+ "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY",
+ "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP",
+ "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT",
+ "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", "OP_AND",
+ "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL",
+ "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV",
+ "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR",
+ "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN",
+ "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX",
+ "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160",
+ "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG",
+ "OP_CHECKMULTISIGVERIFY",
+ ("OP_SINGLEBYTE_END", 0xF0),
+ ("OP_DOUBLEBYTE_BEGIN", 0xF000),
+ "OP_PUBKEY", "OP_PUBKEYHASH",
+ ("OP_INVALIDOPCODE", 0xFFFF),
+])
+
+def script_GetOp(bytes):
+ i = 0
+ while i < len(bytes):
+ vch = None
+ opcode = ord(bytes[i])
+ i += 1
+ if opcode >= opcodes.OP_SINGLEBYTE_END:
+ opcode <<= 8
+ opcode |= bytes[i]
+ i += 1
+
+ if opcode <= opcodes.OP_PUSHDATA4:
+ nSize = opcode
+ if opcode == opcodes.OP_PUSHDATA1:
+ nSize = ord(bytes[i])
+ i += 1
+ elif opcode == opcodes.OP_PUSHDATA2:
+ nSize = unpack_from('<H', bytes, i)
+ i += 2
+ elif opcode == opcodes.OP_PUSHDATA4:
+ nSize = unpack_from('<I', bytes, i)
+ i += 4
+ vch = bytes[i:i+nSize]
+ i += nSize
+
+ yield (opcode, vch)
+
+def script_GetOpName(opcode):
+ return (opcodes.whatis(opcode)).replace("OP_", "")
+
+def decode_script(bytes):
+ result = ''
+ for (opcode, vch) in script_GetOp(bytes):
+ if len(result) > 0: result += " "
+ if opcode <= opcodes.OP_PUSHDATA4:
+ result += "%d:"%(opcode,)
+ result += short_hex(vch)
+ else:
+ result += script_GetOpName(opcode)
+ return result
+
+def match_decoded(decoded, to_match):
+ if len(decoded) != len(to_match):
+ return False;
+ for i in range(len(decoded)):
+ if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4:
+ continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
+ if to_match[i] != decoded[i][0]:
+ return False
+ return True
+
+def extract_public_key(bytes):
+ decoded = [ x for x in script_GetOp(bytes) ]
+
+ # non-generated TxIn transactions push a signature
+ # (seventy-something bytes) and then their public key
+ # (65 bytes) onto the stack:
+ match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ]
+ if match_decoded(decoded, match):
+ return public_key_to_bc_address(decoded[1][1])
+
+ # The Genesis Block, self-payments, and pay-by-IP-address payments look like:
+ # 65 BYTES:... CHECKSIG
+ match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ]
+ if match_decoded(decoded, match):
+ return public_key_to_bc_address(decoded[0][1])
+
+ # Pay-by-Bitcoin-address TxOuts look like:
+ # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
+ match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
+ if match_decoded(decoded, match):
+ return hash_160_to_bc_address(decoded[2][1])
+
+ return "(None)"
--- /dev/null
+#
+# enum-like type
+# From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/
+#
+import types, string, exceptions
+
+class EnumException(exceptions.Exception):
+ pass
+
+class Enumeration:
+ def __init__(self, name, enumList):
+ self.__doc__ = name
+ lookup = { }
+ reverseLookup = { }
+ i = 0
+ uniqueNames = [ ]
+ uniqueValues = [ ]
+ for x in enumList:
+ if type(x) == types.TupleType:
+ x, i = x
+ if type(x) != types.StringType:
+ raise EnumException, "enum name is not a string: " + x
+ if type(i) != types.IntType:
+ raise EnumException, "enum value is not an integer: " + i
+ if x in uniqueNames:
+ raise EnumException, "enum name is not unique: " + x
+ if i in uniqueValues:
+ raise EnumException, "enum value is not unique for " + x
+ uniqueNames.append(x)
+ uniqueValues.append(i)
+ lookup[x] = i
+ reverseLookup[i] = x
+ i = i + 1
+ self.lookup = lookup
+ self.reverseLookup = reverseLookup
+ def __getattr__(self, attr):
+ if not self.lookup.has_key(attr):
+ raise AttributeError
+ return self.lookup[attr]
+ def whatis(self, value):
+ return self.reverseLookup[value]
--- /dev/null
+#!/usr/bin/env python
+#
+# Recover from a semi-corrupt wallet
+#
+from bsddb.db import *
+import logging
+import sys
+
+from wallet import rewrite_wallet, trim_wallet
+from util import determine_db_dir, create_env
+
+def main():
+ import optparse
+ parser = optparse.OptionParser(usage="%prog [options]")
+ parser.add_option("--datadir", dest="datadir", default=None,
+ help="Look for files here (defaults to bitcoin default)")
+ parser.add_option("--out", dest="outfile", default="walletNEW.dat",
+ help="Name of output file (default: walletNEW.dat)")
+ parser.add_option("--clean", action="store_true", dest="clean", default=False,
+ help="Clean out old, spent change addresses and transactions")
+ parser.add_option("--skipkey", dest="skipkey",
+ help="Skip entries with keys that contain given string")
+ parser.add_option("--tweakspent", dest="tweakspent",
+ help="Tweak transaction to mark unspent")
+ (options, args) = parser.parse_args()
+
+ if options.datadir is None:
+ db_dir = determine_db_dir()
+ else:
+ db_dir = options.datadir
+
+ try:
+ db_env = create_env(db_dir)
+ except DBNoSuchFileError:
+ logging.error("Couldn't open " + db_dir)
+ sys.exit(1)
+
+ if options.clean:
+ trim_wallet(db_env, options.outfile)
+
+ elif options.skipkey:
+ def pre_put_callback(type, data):
+ if options.skipkey in data['__key__']:
+ return False
+ return True
+ rewrite_wallet(db_env, options.outfile, pre_put_callback)
+ elif options.tweakspent:
+ txid = options.tweakspent.decode('hex_codec')[::-1]
+ def tweak_spent_callback(type, data):
+ if txid in data['__key__']:
+ data['__value__'] = data['__value__'][:-1]+'\0'
+ return True
+ rewrite_wallet(db_env, options.outfile, tweak_spent_callback)
+ pass
+ else:
+ rewrite_wallet(db_env, options.outfile)
+
+ db_env.close()
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#!/usr/bin/env python
+#
+# Reads an array of JSON objects and writes out CSV-format,
+# with key names in first row.
+# Columns will be union of all keys in the objects.
+#
+
+import csv
+import json
+import sys
+
+json_string = sys.stdin.read()
+json_array = json.loads(json_string)
+
+columns = set()
+for item in json_array:
+ columns.update(set(item))
+
+writer = csv.writer(sys.stdout)
+writer.writerow(list(columns))
+for item in json_array:
+ row = []
+ for c in columns:
+ if c in item: row.append(str(item[c]))
+ else: row.append('')
+ writer.writerow(row)
+
--- /dev/null
+#!/usr/bin/env python
+#
+# Read the block database, generate monthly statistics and dump out
+# a CSV file.
+#
+from bsddb.db import *
+from datetime import date
+import logging
+import os
+import sys
+
+from BCDataStream import *
+from block import scan_blocks
+from collections import defaultdict
+from deserialize import parse_Block
+from util import determine_db_dir, create_env
+
+def main():
+ import optparse
+ parser = optparse.OptionParser(usage="%prog [options]")
+ parser.add_option("--datadir", dest="datadir", default=None,
+ help="Look for files here (defaults to bitcoin default)")
+ (options, args) = parser.parse_args()
+
+ if options.datadir is None:
+ db_dir = determine_db_dir()
+ else:
+ db_dir = options.datadir
+
+ try:
+ db_env = create_env(db_dir)
+ except DBNoSuchFileError:
+ logging.error("Couldn't open " + db_dir)
+ sys.exit(1)
+
+ blockfile = open(os.path.join(db_dir, "blk%04d.dat"%(1,)), "rb")
+ block_datastream = BCDataStream()
+ block_datastream.map_file(blockfile, 0)
+
+ n_transactions = defaultdict(int)
+ v_transactions = defaultdict(float)
+ v_transactions_min = defaultdict(float)
+ v_transactions_max = defaultdict(float)
+ def gather_stats(block_data):
+ block_datastream.seek_file(block_data['nBlockPos'])
+ data = parse_Block(block_datastream)
+ block_date = date.fromtimestamp(data['nTime'])
+ key = "%d-%02d"%(block_date.year, block_date.month)
+ for txn in data['transactions'][1:]:
+ values = []
+ for txout in txn['txOut']:
+ n_transactions[key] += 1
+ v_transactions[key] += txout['value']
+ values.append(txout['value'])
+ v_transactions_min[key] += min(values)
+ v_transactions_max[key] += max(values)
+ return True
+
+ scan_blocks(db_dir, db_env, gather_stats)
+
+ db_env.close()
+
+ keys = n_transactions.keys()
+ keys.sort()
+ for k in keys:
+ v = v_transactions[k]/1.0e8
+ v_min = v_transactions_min[k]/1.0e8
+ v_max = v_transactions_max[k]/1.0e8
+ # Columns are:
+ # month n_transactions min max total
+ # ... where min and max add up just the smallest or largest
+ # output in each transaction; the true value of bitcoins
+ # transferred will be somewhere between min and max.
+ # We don't know how many are transfers-to-self, though, and
+ # this will undercount multi-txout-transactions (which is good
+ # right now, because they're mostly used for mining pool
+ # payouts that arguably shouldn't count).
+ print "%s,%d,%.2f,%.2f,%.2f"%(k, n_transactions[k], v_min, v_max, v)
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#
+# Unit tests for BCDataStream class
+#
+
+import unittest
+
+import BCDataStream
+
+class Tests(unittest.TestCase):
+ def setUp(self):
+ self.ds = BCDataStream.BCDataStream()
+
+ def testString(self):
+ t = {
+ "\x07setting" : "setting",
+ "\xfd\x00\x07setting" : "setting",
+ "\xfe\x00\x00\x00\x07setting" : "setting",
+ }
+ for (input, output) in t.iteritems():
+ self.ds.clear()
+ self.ds.write(input)
+ got = self.ds.read_string()
+ self.assertEqual(output, got)
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+#
+# Code for dumping a single transaction, given its ID
+#
+
+from bsddb.db import *
+import logging
+import os.path
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address
+from util import short_hex
+from deserialize import *
+
+def _read_CDiskTxPos(stream):
+ n_file = stream.read_uint32()
+ n_block_pos = stream.read_uint32()
+ n_tx_pos = stream.read_uint32()
+ return (n_file, n_block_pos, n_tx_pos)
+
+def _dump_tx(datadir, tx_hash, tx_pos):
+ blockfile = open(os.path.join(datadir, "blk%04d.dat"%(tx_pos[0],)), "rb")
+ ds = BCDataStream()
+ ds.map_file(blockfile, tx_pos[2])
+ d = parse_Transaction(ds)
+ print deserialize_Transaction(d)
+ ds.close_file()
+ blockfile.close()
+
+def dump_transaction(datadir, db_env, tx_id):
+ """ Dump a transaction, given hexadecimal tx_id-- either the full ID
+ OR a short_hex version of the id.
+ """
+ db = DB(db_env)
+ try:
+ r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.")
+ sys.exit(1)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ n_tx = 0
+ n_blockindex = 0
+
+ key_prefix = "\x02tx"+(tx_id[-4:].decode('hex_codec')[::-1])
+ cursor = db.cursor()
+ (key, value) = cursor.set_range(key_prefix)
+
+ while key.startswith(key_prefix):
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+ hash256 = (kds.read_bytes(32))
+ hash_hex = long_hex(hash256[::-1])
+ version = vds.read_uint32()
+ tx_pos = _read_CDiskTxPos(vds)
+ if (hash_hex.startswith(tx_id) or short_hex(hash256[::-1]).startswith(tx_id)):
+ _dump_tx(datadir, hash256, tx_pos)
+
+ (key, value) = cursor.next()
+
+ db.close()
+
--- /dev/null
+#
+# Misc util routines
+#
+
+from bsddb.db import *
+
+def long_hex(bytes):
+ return bytes.encode('hex_codec')
+
+def short_hex(bytes):
+ t = bytes.encode('hex_codec')
+ if len(t) < 11:
+ return t
+ return t[0:4]+"..."+t[-4:]
+
+def determine_db_dir():
+ import os
+ import os.path
+ import platform
+ if platform.system() == "Darwin":
+ return os.path.expanduser("~/Library/Application Support/Bitcoin/")
+ elif platform.system() == "Windows":
+ return os.path.join(os.environ['APPDATA'], "Bitcoin")
+ return os.path.expanduser("~/.bitcoin")
+
+def create_env(db_dir=None):
+ if db_dir is None:
+ db_dir = determine_db_dir()
+ db_env = DBEnv(0)
+ r = db_env.open(db_dir,
+ (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|
+ DB_INIT_TXN|DB_THREAD|DB_RECOVER))
+ return db_env
--- /dev/null
+#
+# Code for parsing the wallet.dat file
+#
+
+from bsddb.db import *
+import logging
+import re
+import sys
+import time
+
+from BCDataStream import *
+from base58 import public_key_to_bc_address, bc_address_to_hash_160, hash_160
+from util import short_hex, long_hex
+from deserialize import *
+
+def open_wallet(db_env, writable=False):
+ db = DB(db_env)
+ flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY)
+ try:
+ r = db.open("wallet.dat", "main", DB_BTREE, flags)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.")
+ sys.exit(1)
+
+ return db
+
+def parse_wallet(db, item_callback):
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ for (key, value) in db.items():
+ d = { }
+
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+
+ d["__key__"] = key
+ d["__value__"] = value
+ d["__type__"] = type
+
+ try:
+ if type == "tx":
+ d["tx_id"] = kds.read_bytes(32)
+ d.update(parse_WalletTx(vds))
+ elif type == "name":
+ d['hash'] = kds.read_string()
+ d['name'] = vds.read_string()
+ elif type == "version":
+ d['version'] = vds.read_uint32()
+ elif type == "setting":
+ d['setting'] = kds.read_string()
+ d['value'] = parse_setting(d['setting'], vds)
+ elif type == "key":
+ d['public_key'] = kds.read_bytes(kds.read_compact_size())
+ d['private_key'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "wkey":
+ d['public_key'] = kds.read_bytes(kds.read_compact_size())
+ d['private_key'] = vds.read_bytes(vds.read_compact_size())
+ d['created'] = vds.read_int64()
+ d['expires'] = vds.read_int64()
+ d['comment'] = vds.read_string()
+ elif type == "ckey":
+ d['public_key'] = kds.read_bytes(kds.read_compact_size())
+ d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "mkey":
+ d['nID'] = kds.read_int32()
+ d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
+ d['salt'] = vds.read_bytes(vds.read_compact_size())
+ d['nDerivationMethod'] = vds.read_int32()
+ d['nDeriveIterations'] = vds.read_int32()
+ d['vchOtherDerivationParameters'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "defaultkey":
+ d['key'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "pool":
+ d['n'] = kds.read_int64()
+ d['nVersion'] = vds.read_int32()
+ d['nTime'] = vds.read_int64()
+ d['public_key'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "acc":
+ d['account'] = kds.read_string()
+ d['nVersion'] = vds.read_int32()
+ d['public_key'] = vds.read_bytes(vds.read_compact_size())
+ elif type == "acentry":
+ d['account'] = kds.read_string()
+ d['n'] = kds.read_uint64()
+ d['nVersion'] = vds.read_int32()
+ d['nCreditDebit'] = vds.read_int64()
+ d['nTime'] = vds.read_int64()
+ d['otherAccount'] = vds.read_string()
+ d['comment'] = vds.read_string()
+ elif type == "bestblock":
+ d['nVersion'] = vds.read_int32()
+ d.update(parse_BlockLocator(vds))
+ else:
+ print "Unknown key type: "+type
+
+ item_callback(type, d)
+
+ except Exception, e:
+ print("ERROR parsing wallet.dat, type %s"%type)
+ print("key data in hex: %s"%key.encode('hex_codec'))
+ print("value data in hex: %s"%value.encode('hex_codec'))
+
+def update_wallet(db, type, data):
+ """Write a single item to the wallet.
+ db must be open with writable=True.
+ type and data are the type code and data dictionary as parse_wallet would
+ give to item_callback.
+ data's __key__, __value__ and __type__ are ignored; only the primary data
+ fields are used.
+ """
+ d = data
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ # Write the type code to the key
+ kds.write_string(type)
+ vds.write("") # Ensure there is something
+
+ try:
+ if type == "tx":
+ raise NotImplementedError("Writing items of type 'tx'")
+ kds.write(d['tx_id'])
+ #d.update(parse_WalletTx(vds))
+ elif type == "name":
+ kds.write(d['hash'])
+ vds.write(d['name'])
+ elif type == "version":
+ vds.write_uint32(d['version'])
+ elif type == "setting":
+ raise NotImplementedError("Writing items of type 'setting'")
+ kds.write_string(d['setting'])
+ #d['value'] = parse_setting(d['setting'], vds)
+ elif type == "key":
+ kds.write_string(d['public_key'])
+ vds.write_string(d['private_key'])
+ elif type == "wkey":
+ kds.write_string(d['public_key'])
+ vds.write_string(d['private_key'])
+ vds.write_int64(d['created'])
+ vds.write_int64(d['expires'])
+ vds.write_string(d['comment'])
+ elif type == "ckey":
+ kds.write_string(d['public_key'])
+ kds.write_string(d['crypted_key'])
+ elif type == "mkey":
+ kds.write_int32(d['nID'])
+ vds.write_string(d['crypted_key'])
+ vds.write_string(d['salt'])
+ vds.write_int32(d['nDeriveIterations'])
+ vds.write_int32(d['nDerivationMethod'])
+ vds.write_string(d['vchOtherDerivationParameters'])
+ elif type == "defaultkey":
+ vds.write_string(d['key'])
+ elif type == "pool":
+ kds.write_int64(d['n'])
+ vds.write_int32(d['nVersion'])
+ vds.write_int64(d['nTime'])
+ vds.write_string(d['public_key'])
+ elif type == "acc":
+ kds.write_string(d['account'])
+ vds.write_int32(d['nVersion'])
+ vds.write_string(d['public_key'])
+ elif type == "acentry":
+ kds.write_string(d['account'])
+ kds.write_uint64(d['n'])
+ vds.write_int32(d['nVersion'])
+ vds.write_int64(d['nCreditDebit'])
+ vds.write_int64(d['nTime'])
+ vds.write_string(d['otherAccount'])
+ vds.write_string(d['comment'])
+ elif type == "bestblock":
+ vds.write_int32(d['nVersion'])
+ vds.write_compact_size(len(d['hashes']))
+ for h in d['hashes']:
+ vds.write(h)
+ else:
+ print "Unknown key type: "+type
+
+ # Write the key/value pair to the database
+ db.put(kds.input, vds.input)
+
+ except Exception, e:
+ print("ERROR writing to wallet.dat, type %s"%type)
+ print("data dictionary: %r"%data)
+
+def dump_wallet(db_env, print_wallet, print_wallet_transactions, transaction_filter):
+ db = open_wallet(db_env)
+
+ wallet_transactions = []
+ transaction_index = { }
+ owner_keys = { }
+
+ def item_callback(type, d):
+ if type == "tx":
+ wallet_transactions.append( d )
+ transaction_index[d['tx_id']] = d
+ elif type == "key":
+ owner_keys[public_key_to_bc_address(d['public_key'])] = d['private_key']
+ elif type == "ckey":
+ owner_keys[public_key_to_bc_address(d['public_key'])] = d['crypted_key']
+
+ if not print_wallet:
+ return
+ if type == "tx":
+ return
+ elif type == "name":
+ print("ADDRESS "+d['hash']+" : "+d['name'])
+ elif type == "version":
+ print("Version: %d"%(d['version'],))
+ elif type == "setting":
+ print(d['setting']+": "+str(d['value']))
+ elif type == "key":
+ print("PubKey "+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) +
+ ": PriKey "+ short_hex(d['private_key']))
+ elif type == "wkey":
+ print("WPubKey 0x"+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) +
+ ": WPriKey 0x"+ short_hex(d['crypted_key']))
+ print(" Created: "+time.ctime(d['created'])+" Expires: "+time.ctime(d['expires'])+" Comment: "+d['comment'])
+ elif type == "ckey":
+ print("PubKey "+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) +
+ ": Encrypted PriKey "+ short_hex(d['crypted_key']))
+ elif type == "mkey":
+ print("Master Key %d"%(d['nID']) + ": 0x"+ short_hex(d['crypted_key']) +
+ ", Salt: 0x"+ short_hex(d['salt']) +
+ ". Passphrase hashed %d times with method %d with other parameters 0x"%(d['nDeriveIterations'], d['nDerivationMethod']) +
+ long_hex(d['vchOtherDerivationParameters']))
+ elif type == "defaultkey":
+ print("Default Key: 0x"+ short_hex(d['key']) + " " + public_key_to_bc_address(d['key']))
+ elif type == "pool":
+ print("Change Pool key %d: %s (Time: %s)"% (d['n'], public_key_to_bc_address(d['public_key']), time.ctime(d['nTime'])))
+ elif type == "acc":
+ print("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
+ elif type == "acentry":
+ print("Move '%s' %d (other: '%s', time: %s, entry %d) %s"%
+ (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment']))
+ elif type == "bestblock":
+ print deserialize_BlockLocator(d)
+ else:
+ print "Unknown key type: "+type
+
+ parse_wallet(db, item_callback)
+
+ if print_wallet_transactions:
+ keyfunc = lambda i: i['timeReceived']
+ for d in sorted(wallet_transactions, key=keyfunc):
+ tx_value = deserialize_WalletTx(d, transaction_index, owner_keys)
+ if len(transaction_filter) > 0 and re.search(transaction_filter, tx_value) is None: continue
+
+ print("==WalletTransaction== "+long_hex(d['tx_id'][::-1]))
+ print(tx_value)
+
+ db.close()
+
+def dump_accounts(db_env):
+ db = open_wallet(db_env)
+
+ kds = BCDataStream()
+ vds = BCDataStream()
+
+ accounts = set()
+
+ for (key, value) in db.items():
+ kds.clear(); kds.write(key)
+ vds.clear(); vds.write(value)
+
+ type = kds.read_string()
+
+ if type == "acc":
+ accounts.add(kds.read_string())
+ elif type == "name":
+ accounts.add(vds.read_string())
+ elif type == "acentry":
+ accounts.add(kds.read_string())
+ # Note: don't need to add otheraccount, because moves are
+ # always double-entry
+
+ for name in sorted(accounts):
+ print(name)
+
+ db.close()
+
+def rewrite_wallet(db_env, destFileName, pre_put_callback=None):
+ db = open_wallet(db_env)
+
+ db_out = DB(db_env)
+ try:
+ r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open %s."%destFileName)
+ sys.exit(1)
+
+ def item_callback(type, d):
+ if (pre_put_callback is None or pre_put_callback(type, d)):
+ db_out.put(d["__key__"], d["__value__"])
+
+ parse_wallet(db, item_callback)
+
+ db_out.close()
+ db.close()
+
+def trim_wallet(db_env, destFileName, pre_put_callback=None):
+ """Write out ONLY address book public/private keys
+ THIS WILL NOT WRITE OUT 'change' KEYS-- you should
+ send all of your bitcoins to one of your public addresses
+ before calling this.
+ """
+ db = open_wallet(db_env)
+
+ pubkeys = []
+ def gather_pubkeys(type, d):
+ if type == "name":
+ pubkeys.append(bc_address_to_hash_160(d['hash']))
+
+ parse_wallet(db, gather_pubkeys)
+
+ db_out = DB(db_env)
+ try:
+ r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE)
+ except DBError:
+ r = True
+
+ if r is not None:
+ logging.error("Couldn't open %s."%destFileName)
+ sys.exit(1)
+
+ def item_callback(type, d):
+ should_write = False
+ if type in [ 'version', 'name', 'acc' ]:
+ should_write = True
+ if type in [ 'key', 'wkey', 'ckey' ] and hash_160(d['public_key']) in pubkeys:
+ should_write = True
+ if pre_put_callback is not None:
+ should_write = pre_put_callback(type, d, pubkeys)
+ if should_write:
+ db_out.put(d["__key__"], d["__value__"])
+
+ parse_wallet(db, item_callback)
+
+ db_out.close()
+ db.close()