├── util.py ├── testBCDataStream.py ├── address.py ├── LICENSE.txt ├── blkindex.py ├── enumeration.py ├── fixwallet.py ├── README.md ├── blocks.py ├── transaction.py ├── base58.py ├── BCDataStream.py ├── dbdump.py ├── wallet.py ├── block.py ├── deserialize.py └── NOTES.txt /util.py: -------------------------------------------------------------------------------- 1 | # 2 | # Misc util routines 3 | # 4 | 5 | def long_hex(bytes): 6 | return bytes.encode('hex_codec') 7 | 8 | def short_hex(bytes): 9 | t = bytes.encode('hex_codec') 10 | if len(t) < 11: 11 | return t 12 | return t[0:4]+"..."+t[-4:] 13 | 14 | -------------------------------------------------------------------------------- /testBCDataStream.py: -------------------------------------------------------------------------------- 1 | # 2 | # Unit tests for BCDataStream class 3 | # 4 | 5 | import unittest 6 | 7 | import BCDataStream 8 | 9 | class Tests(unittest.TestCase): 10 | def setUp(self): 11 | self.ds = BCDataStream.BCDataStream() 12 | 13 | def testString(self): 14 | t = { 15 | "\x07setting" : "setting", 16 | "\xfd\x00\x07setting" : "setting", 17 | "\xfe\x00\x00\x00\x07setting" : "setting", 18 | } 19 | for (input, output) in t.iteritems(): 20 | self.ds.clear() 21 | self.ds.write(input) 22 | got = self.ds.read_string() 23 | self.assertEqual(output, got) 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /address.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for parsing the addr.dat file 3 | # NOTE: I think you have to shutdown the Bitcoin client to 4 | # successfully read addr.dat... 5 | # 6 | 7 | from bsddb.db import * 8 | import logging 9 | from operator import itemgetter 10 | import sys 11 | import time 12 | 13 | from BCDataStream import * 14 | from base58 import public_key_to_bc_address 15 | from util import short_hex 16 | from deserialize import * 17 | 18 | def dump_addresses(db_env): 19 | db = DB(db_env) 20 | try: 21 | r = db.open("addr.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 22 | except DBError: 23 | r = True 24 | 25 | if r is not None: 26 | logging.error("Couldn't open addr.dat/main. Try quitting Bitcoin and running this again.") 27 | sys.exit(1) 28 | 29 | kds = BCDataStream() 30 | vds = BCDataStream() 31 | 32 | for (key, value) in db.items(): 33 | kds.clear(); kds.write(key) 34 | vds.clear(); vds.write(value) 35 | 36 | type = kds.read_string() 37 | 38 | if type == "addr": 39 | d = parse_CAddress(vds) 40 | print(deserialize_CAddress(d)) 41 | 42 | db.close() 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Gavin Andresen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /blkindex.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for parsing the blkindex.dat file 3 | # 4 | 5 | from bsddb.db import * 6 | import logging 7 | from operator import itemgetter 8 | import sys 9 | import time 10 | 11 | from BCDataStream import * 12 | from base58 import public_key_to_bc_address 13 | from util import short_hex 14 | from deserialize import * 15 | 16 | def dump_blkindex_summary(db_env): 17 | db = DB(db_env) 18 | try: 19 | r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 20 | except DBError: 21 | r = True 22 | 23 | if r is not None: 24 | logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") 25 | sys.exit(1) 26 | 27 | kds = BCDataStream() 28 | vds = BCDataStream() 29 | 30 | n_tx = 0 31 | n_blockindex = 0 32 | 33 | print("blkindex file summary:") 34 | for (key, value) in db.items(): 35 | kds.clear(); kds.write(key) 36 | vds.clear(); vds.write(value) 37 | 38 | type = kds.read_string() 39 | 40 | if type == "tx": 41 | n_tx += 1 42 | elif type == "blockindex": 43 | n_blockindex += 1 44 | elif type == "version": 45 | version = vds.read_int32() 46 | print(" Version: %d"%(version,)) 47 | elif type == "hashBestChain": 48 | hash = vds.read_bytes(32) 49 | print(" HashBestChain: %s"%(hash.encode('hex_codec'),)) 50 | else: 51 | logging.warn("blkindex: unknown type '%s'"%(type,)) 52 | continue 53 | 54 | print(" %d transactions, %d blocks."%(n_tx, n_blockindex)) 55 | db.close() 56 | -------------------------------------------------------------------------------- /enumeration.py: -------------------------------------------------------------------------------- 1 | # 2 | # enum-like type 3 | # From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/ 4 | # 5 | import types, string, exceptions 6 | 7 | class EnumException(exceptions.Exception): 8 | pass 9 | 10 | class Enumeration: 11 | def __init__(self, name, enumList): 12 | self.__doc__ = name 13 | lookup = { } 14 | reverseLookup = { } 15 | i = 0 16 | uniqueNames = [ ] 17 | uniqueValues = [ ] 18 | for x in enumList: 19 | if type(x) == types.TupleType: 20 | x, i = x 21 | if type(x) != types.StringType: 22 | raise EnumException, "enum name is not a string: " + x 23 | if type(i) != types.IntType: 24 | raise EnumException, "enum value is not an integer: " + i 25 | if x in uniqueNames: 26 | raise EnumException, "enum name is not unique: " + x 27 | if i in uniqueValues: 28 | raise EnumException, "enum value is not unique for " + x 29 | uniqueNames.append(x) 30 | uniqueValues.append(i) 31 | lookup[x] = i 32 | reverseLookup[i] = x 33 | i = i + 1 34 | self.lookup = lookup 35 | self.reverseLookup = reverseLookup 36 | def __getattr__(self, attr): 37 | if not self.lookup.has_key(attr): 38 | raise AttributeError 39 | return self.lookup[attr] 40 | def whatis(self, value): 41 | return self.reverseLookup[value] 42 | -------------------------------------------------------------------------------- /fixwallet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Recover from a semi-corrupt wallet 4 | # 5 | from bsddb.db import * 6 | import logging 7 | import sys 8 | 9 | from wallet import rewrite_wallet 10 | 11 | def determine_db_dir(): 12 | import os 13 | import os.path 14 | import platform 15 | if platform.system() == "Darwin": 16 | return os.path.expanduser("~/Library/Application Support/Bitcoin/") 17 | elif platform.system() == "Windows": 18 | return os.path.join(os.environ['APPDATA'], "Bitcoin") 19 | return os.path.expanduser("~/.bitcoin") 20 | 21 | def main(): 22 | import optparse 23 | parser = optparse.OptionParser(usage="%prog [options]") 24 | parser.add_option("--datadir", dest="datadir", default=None, 25 | help="Look for files here (defaults to bitcoin default)") 26 | parser.add_option("--out", dest="outfile", default="walletNEW.dat", 27 | help="Name of output file (default: walletNEW.dat)") 28 | (options, args) = parser.parse_args() 29 | 30 | if options.datadir is None: 31 | db_dir = determine_db_dir() 32 | else: 33 | db_dir = options.datadir 34 | 35 | db_env = DBEnv(0) 36 | r = db_env.open(db_dir, 37 | (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL| 38 | DB_INIT_TXN|DB_THREAD|DB_RECOVER)) 39 | 40 | if r is not None: 41 | logging.error("Couldn't open "+DB_DIR) 42 | sys.exit(1) 43 | 44 | rewrite_wallet(db_env, options.outfile) 45 | 46 | db_env.close() 47 | 48 | if __name__ == '__main__': 49 | main() 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ------------------------- 4 | ### Run Google Colab 5 | 6 | https://colab.research.google.com/drive/1OShIMVcFZ_khsUIBOIV1lzrqAGo1gfm_?usp=sharing 7 | 8 | ------------------------- 9 | 10 | Run dbdump.py --help for usage. Database files are opened read-only, but 11 | you might want to backup your Bitcoin wallet.dat file just in case. 12 | 13 | You must quit Bitcoin before reading the transactions, blocks, or address database files. 14 | 15 | Requires the pycrypto library from http://www.dlitz.net/software/pycrypto/ 16 | to translate public keys into human-friendly Bitcoin addresses. 17 | 18 | Examples: 19 | 20 | Print out wallet keys and transactions: 21 | dbdump.py --wallet --wallet-tx 22 | 23 | Print out the "genesis block" (the very first block in the proof-of-work block chain): 24 | dbdump.py --block=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 25 | 26 | Print out one of the transactions from my wallet: 27 | dbdump.py --transaction=c6e1bf883bceef0aa05113e189982055d9ba7212ddfc879798616a0d0828c98c 28 | dbdump.py --transaction=c6e1...c98c 29 | 30 | Print out all 'received' transactions that aren't yet spent: 31 | dbdump.py --wallet-tx-filter='fromMe:False.*spent:False' 32 | 33 | Print out all blocks involving transactions to the Bitcoin Faucet: 34 | dbdump.py --search-blocks=15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC 35 | 36 | There's a special search term to look for non-standard transactions: 37 | dbdump.py --search-blocks=NONSTANDARD_CSCRIPTS 38 | 39 | ---- 40 | 41 | | | Donation Address | 42 | | --- | --- | 43 | | ♥ __BTC__ | 1Lw2kh9WzCActXSGHxyypGLkqQZfxDpw8v | 44 | | ♥ __ETH__ | 0xaBd66CF90898517573f19184b3297d651f7b90bf | 45 | -------------------------------------------------------------------------------- /blocks.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for parsing the blkindex.dat file 3 | # 4 | 5 | from bsddb.db import * 6 | import logging 7 | from operator import itemgetter 8 | import sys 9 | import time 10 | 11 | from BCDataStream import * 12 | from base58 import public_key_to_bc_address 13 | from util import short_hex 14 | from deserialize import * 15 | 16 | def _read_CDiskTxPos(stream): 17 | n_file = stream.read_uint32() 18 | n_block_pos = stream.read_uint32() 19 | n_tx_pos = stream.read_uint32() 20 | return (n_file, n_block_pos, n_tx_pos) 21 | 22 | def dump_blockindex(db_env, owner=None, n_to_dump=1000): 23 | db = DB(db_env) 24 | r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 25 | if r is not None: 26 | logging.error("Couldn't open blkindex.dat/main") 27 | sys.exit(1) 28 | 29 | kds = BCDataStream() 30 | vds = BCDataStream() 31 | 32 | wallet_transactions = [] 33 | 34 | for (i, (key, value)) in enumerate(db.items()): 35 | if i > n_to_dump: 36 | break 37 | 38 | kds.clear(); kds.write(key) 39 | vds.clear(); vds.write(value) 40 | 41 | type = kds.read_string() 42 | 43 | if type == "tx": 44 | hash256 = kds.read_bytes(32) 45 | version = vds.read_uint32() 46 | tx_pos = _read_CDiskTxPos(vds) 47 | print("Tx(%s:%d %d %d)"%((short_hex(hash256),)+tx_pos)) 48 | n_tx_out = vds.read_compact_size() 49 | for i in range(0,n_tx_out): 50 | tx_out = _read_CDiskTxPos(vds) 51 | if tx_out[0] != 0xffffffffL: # UINT_MAX means no TxOuts (unspent) 52 | print(" ==> TxOut(%d %d %d)"%tx_out) 53 | 54 | else: 55 | logging.warn("blkindex: type %s"%(type,)) 56 | continue 57 | 58 | db.close() 59 | 60 | -------------------------------------------------------------------------------- /transaction.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for dumping a single transaction, given its ID 3 | # 4 | 5 | from bsddb.db import * 6 | import logging 7 | import os.path 8 | import sys 9 | import time 10 | 11 | from BCDataStream import * 12 | from base58 import public_key_to_bc_address 13 | from util import short_hex 14 | from deserialize import * 15 | 16 | def _read_CDiskTxPos(stream): 17 | n_file = stream.read_uint32() 18 | n_block_pos = stream.read_uint32() 19 | n_tx_pos = stream.read_uint32() 20 | return (n_file, n_block_pos, n_tx_pos) 21 | 22 | def _dump_tx(datadir, tx_hash, tx_pos): 23 | blockfile = open(os.path.join(datadir, "blk%04d.dat"%(tx_pos[0],)), "rb") 24 | ds = BCDataStream() 25 | ds.map_file(blockfile, tx_pos[2]) 26 | d = parse_Transaction(ds) 27 | print deserialize_Transaction(d) 28 | ds.close_file() 29 | blockfile.close() 30 | 31 | def dump_transaction(datadir, db_env, tx_id): 32 | """ Dump a transaction, given hexadecimal tx_id-- either the full ID 33 | OR a short_hex version of the id. 34 | """ 35 | db = DB(db_env) 36 | try: 37 | r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 38 | except DBError: 39 | r = True 40 | 41 | if r is not None: 42 | logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") 43 | sys.exit(1) 44 | 45 | kds = BCDataStream() 46 | vds = BCDataStream() 47 | 48 | n_tx = 0 49 | n_blockindex = 0 50 | 51 | key_prefix = "\x02tx"+(tx_id[-4:].decode('hex_codec')[::-1]) 52 | cursor = db.cursor() 53 | (key, value) = cursor.set_range(key_prefix) 54 | 55 | while key.startswith(key_prefix): 56 | kds.clear(); kds.write(key) 57 | vds.clear(); vds.write(value) 58 | 59 | type = kds.read_string() 60 | hash256 = (kds.read_bytes(32)) 61 | hash_hex = long_hex(hash256[::-1]) 62 | version = vds.read_uint32() 63 | tx_pos = _read_CDiskTxPos(vds) 64 | if (hash_hex.startswith(tx_id) or short_hex(hash256[::-1]).startswith(tx_id)): 65 | _dump_tx(datadir, hash256, tx_pos) 66 | 67 | (key, value) = cursor.next() 68 | 69 | db.close() 70 | 71 | -------------------------------------------------------------------------------- /base58.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """encode/decode base58 in the same way that Bitcoin does""" 4 | 5 | import math 6 | 7 | __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 8 | __b58base = len(__b58chars) 9 | 10 | def b58encode(v): 11 | """ encode v, which is a string of bytes, to base58. 12 | """ 13 | 14 | long_value = 0L 15 | for (i, c) in enumerate(v[::-1]): 16 | long_value += (256**i) * ord(c) 17 | 18 | result = '' 19 | while long_value >= __b58base: 20 | div, mod = divmod(long_value, __b58base) 21 | result = __b58chars[mod] + result 22 | long_value = div 23 | result = __b58chars[long_value] + result 24 | 25 | # Bitcoin does a little leading-zero-compression: 26 | # leading 0-bytes in the input become leading-1s 27 | nPad = 0 28 | for c in v: 29 | if c == '\0': nPad += 1 30 | else: break 31 | 32 | return (__b58chars[0]*nPad) + result 33 | 34 | def b58decode(v, length): 35 | """ decode v into a string of len bytes 36 | """ 37 | long_value = 0L 38 | for (i, c) in enumerate(v[::-1]): 39 | long_value += __b58chars.find(c) * (__b58base**i) 40 | 41 | result = '' 42 | while long_value >= 256: 43 | div, mod = divmod(long_value, 256) 44 | result = chr(mod) + result 45 | long_value = div 46 | result = chr(long_value) + result 47 | 48 | nPad = 0 49 | for c in v: 50 | if c == __b58chars[0]: nPad += 1 51 | else: break 52 | 53 | result = chr(0)*nPad + result 54 | if length is not None and len(result) != length: 55 | return None 56 | 57 | return result 58 | 59 | try: 60 | # Python Crypto library is at: http://www.dlitz.net/software/pycrypto/ 61 | # Needed for RIPEMD160 hash function, used to compute 62 | # Bitcoin addresses from internal public keys. 63 | from Crypto.Hash import * 64 | have_crypto = True 65 | except ImportError: 66 | have_crypto = False 67 | 68 | def hash_160(public_key): 69 | if not have_crypto: 70 | return '' 71 | h1 = SHA256.new(public_key).digest() 72 | h2 = RIPEMD160.new(h1).digest() 73 | return h2 74 | 75 | def public_key_to_bc_address(public_key): 76 | if not have_crypto: 77 | return '' 78 | h160 = hash_160(public_key) 79 | return hash_160_to_bc_address(h160) 80 | 81 | def hash_160_to_bc_address(h160): 82 | if not have_crypto: 83 | return '' 84 | vh160 = "\x00"+h160 # \x00 is version 0 85 | h3=SHA256.new(SHA256.new(vh160).digest()).digest() 86 | addr=vh160+h3[0:4] 87 | return b58encode(addr) 88 | 89 | def bc_address_to_hash_160(addr): 90 | bytes = b58decode(addr, 25) 91 | return bytes[1:21] 92 | 93 | if __name__ == '__main__': 94 | x = '005cc87f4a3fdfe3a2346b6953267ca867282630d3f9b78e64'.decode('hex_codec') 95 | encoded = b58encode(x) 96 | print encoded, '19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT' 97 | print b58decode(encoded, len(x)).encode('hex_codec'), x.encode('hex_codec') 98 | -------------------------------------------------------------------------------- /BCDataStream.py: -------------------------------------------------------------------------------- 1 | # 2 | # Workalike python implementation of Bitcoin's CDataStream class. 3 | # 4 | import struct 5 | import StringIO 6 | import mmap 7 | 8 | class SerializationError(Exception): 9 | """ Thrown when there's a problem deserializing or serializing """ 10 | 11 | class BCDataStream(object): 12 | def __init__(self): 13 | self.input = None 14 | self.read_cursor = 0 15 | 16 | def clear(self): 17 | self.input = None 18 | self.read_cursor = 0 19 | 20 | def write(self, bytes): # Initialize with string of bytes 21 | if self.input is None: 22 | self.input = bytes 23 | else: 24 | self.input += bytes 25 | 26 | def map_file(self, file, start): # Initialize with bytes from file 27 | self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) 28 | self.read_cursor = start 29 | def close_file(self): 30 | self.input.close() 31 | 32 | def read_string(self): 33 | # Strings are encoded depending on length: 34 | # 0 to 253 : 1-byte-length followed by bytes (if any) 35 | # 254 to 65,535 : byte'253' 2-byte-length followed by bytes 36 | # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes 37 | # ... and the Bitcoin client is coded to understand: 38 | # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string 39 | # ... but I don't think it actually handles any strings that big. 40 | if self.input is None: 41 | raise SerializationError("call write(bytes) before trying to deserialize") 42 | 43 | try: 44 | length = self.read_compact_size() 45 | except IndexError: 46 | raise SerializationError("attempt to read past end of buffer") 47 | 48 | return self.read_bytes(length) 49 | 50 | def read_bytes(self, length): 51 | try: 52 | result = self.input[self.read_cursor:self.read_cursor+length] 53 | self.read_cursor += length 54 | return result 55 | except IndexError: 56 | raise SerializationError("attempt to read past end of buffer") 57 | 58 | return '' 59 | 60 | def read_boolean(self): return self.read_bytes(1)[0] != '\0' 61 | def read_int16(self): return self._read_num(' 0: 68 | dump_tx = True 69 | if options.dump_wallet or dump_tx: 70 | dump_wallet(db_env, options.dump_wallet, dump_tx, options.wallet_tx_filter) 71 | if options.dump_accounts: 72 | dump_accounts(db_env) 73 | 74 | if options.dump_addr: 75 | dump_addresses(db_env) 76 | 77 | if options.check_chain: 78 | check_block_chain(db_env) 79 | 80 | if options.dump_blkindex: 81 | dump_blkindex_summary(db_env) 82 | 83 | if options.dump_transaction is not None: 84 | dump_transaction(db_dir, db_env, options.dump_transaction) 85 | 86 | if options.dump_block is not None: 87 | if len(options.dump_block) < 7: # Probably an integer... 88 | try: 89 | dump_block_n(db_dir, db_env, int(options.dump_block)) 90 | except ValueError: 91 | dump_block(db_dir, db_env, options.dump_block) 92 | else: 93 | dump_block(db_dir, db_env, options.dump_block) 94 | 95 | if options.search_blocks is not None: 96 | search_blocks(db_dir, db_env, options.search_blocks) 97 | 98 | db_env.close() 99 | 100 | if __name__ == '__main__': 101 | main() 102 | -------------------------------------------------------------------------------- /wallet.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for parsing the wallet.dat file 3 | # 4 | 5 | from bsddb.db import * 6 | import logging 7 | import pdb 8 | import re 9 | import sys 10 | import time 11 | 12 | from BCDataStream import * 13 | from base58 import public_key_to_bc_address 14 | from util import short_hex, long_hex 15 | from deserialize import * 16 | 17 | def open_wallet(db_env): 18 | db = DB(db_env) 19 | try: 20 | r = db.open("wallet.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 21 | except DBError: 22 | r = True 23 | 24 | if r is not None: 25 | logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.") 26 | sys.exit(1) 27 | 28 | return db 29 | 30 | def parse_wallet(db, item_callback): 31 | kds = BCDataStream() 32 | vds = BCDataStream() 33 | 34 | for (key, value) in db.items(): 35 | d = { } 36 | 37 | kds.clear(); kds.write(key) 38 | vds.clear(); vds.write(value) 39 | 40 | type = kds.read_string() 41 | 42 | d["__key__"] = key 43 | d["__value__"] = value 44 | d["__type__"] = type 45 | 46 | try: 47 | if type == "tx": 48 | d["tx_id"] = kds.read_bytes(32) 49 | d.update(parse_WalletTx(vds)) 50 | elif type == "name": 51 | d['hash'] = kds.read_string() 52 | d['name'] = vds.read_string() 53 | elif type == "version": 54 | d['version'] = vds.read_uint32() 55 | elif type == "setting": 56 | d['setting'] = kds.read_string() 57 | d['value'] = parse_setting(d['setting'], vds) 58 | elif type == "key": 59 | d['public_key'] = kds.read_bytes(kds.read_compact_size()) 60 | d['private_key'] = vds.read_bytes(vds.read_compact_size()) 61 | elif type == "wkey": 62 | d['public_key'] = kds.read_bytes(kds.read_compact_size()) 63 | d['private_key'] = vds.read_bytes(vds.read_compact_size()) 64 | d['created'] = vds.read_int64() 65 | d['expires'] = vds.read_int64() 66 | d['comment'] = vds.read_string() 67 | elif type == "defaultkey": 68 | d['key'] = vds.read_bytes(vds.read_compact_size()) 69 | elif type == "pool": 70 | d['n'] = kds.read_int64() 71 | d['nVersion'] = vds.read_int32() 72 | d['nTime'] = vds.read_int64() 73 | d['public_key'] = vds.read_bytes(vds.read_compact_size()) 74 | elif type == "acc": 75 | d['account'] = kds.read_string() 76 | d['nVersion'] = vds.read_int32() 77 | d['public_key'] = vds.read_bytes(vds.read_compact_size()) 78 | elif type == "acentry": 79 | d['account'] = kds.read_string() 80 | d['n'] = kds.read_uint64() 81 | d['nVersion'] = vds.read_int32() 82 | d['nCreditDebit'] = vds.read_int64() 83 | d['nTime'] = vds.read_int64() 84 | d['otherAccount'] = vds.read_string() 85 | d['comment'] = vds.read_string() 86 | else: 87 | print "Unknown key type: "+type 88 | 89 | item_callback(type, d) 90 | 91 | except Exception, e: 92 | print("ERROR parsing wallet.dat, type %s"%type) 93 | print("key data in hex: %s"%key.encode('hex_codec')) 94 | print("value data in hex: %s"%value.encode('hex_codec')) 95 | 96 | 97 | def dump_wallet(db_env, print_wallet, print_wallet_transactions, transaction_filter): 98 | db = open_wallet(db_env) 99 | 100 | wallet_transactions = [] 101 | 102 | def item_callback(type, d): 103 | if type == "tx": 104 | wallet_transactions.append( d ) 105 | elif print_wallet: 106 | if type == "name": 107 | print("ADDRESS "+d['hash']+" : "+d['name']) 108 | elif type == "version": 109 | print("Version: %d"%(d['version'],)) 110 | elif type == "setting": 111 | print(d['setting']+": "+str(d['value'])) 112 | elif type == "key": 113 | print("PubKey "+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) + 114 | ": PriKey "+ short_hex(d['private_key'])) 115 | elif type == "wkey": 116 | print("WPubKey 0x"+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) + 117 | ": WPriKey 0x"+ short_hex(d['private_key'])) 118 | print(" Created: "+time.ctime(d['created'])+" Expires: "+time.ctime(d['expires'])+" Comment: "+d['comment']) 119 | elif type == "defaultkey": 120 | print("Default Key: 0x"+ short_hex(d['key']) + " " + public_key_to_bc_address(d['key'])) 121 | elif type == "pool": 122 | print("Change Pool key %d: %s (Time: %s)"% (d['n'], public_key_to_bc_address(d['public_key']), time.ctime(d['nTime']))) 123 | elif type == "acc": 124 | print("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key']))) 125 | elif type == "acentry": 126 | print("Move '%s' %d (other: '%s', time: %s, entry %d) %s"% 127 | (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])) 128 | else: 129 | print "Unknown key type: "+type 130 | 131 | parse_wallet(db, item_callback) 132 | 133 | if print_wallet_transactions: 134 | sortfunc = lambda t1, t2: t1['timeReceived'] < t2['timeReceived'] 135 | for d in sorted(wallet_transactions, cmp=sortfunc): 136 | tx_value = deserialize_WalletTx(d) 137 | if len(transaction_filter) > 0 and re.search(transaction_filter, tx_value) is None: continue 138 | 139 | print("==WalletTransaction== "+long_hex(d['tx_id'][::-1])) 140 | print(tx_value) 141 | 142 | db.close() 143 | 144 | def dump_accounts(db_env): 145 | db = open_wallet(db_env) 146 | 147 | kds = BCDataStream() 148 | vds = BCDataStream() 149 | 150 | accounts = set() 151 | 152 | for (key, value) in db.items(): 153 | kds.clear(); kds.write(key) 154 | vds.clear(); vds.write(value) 155 | 156 | type = kds.read_string() 157 | 158 | if type == "acc": 159 | accounts.add(kds.read_string()) 160 | elif type == "name": 161 | accounts.add(vds.read_string()) 162 | elif type == "acentry": 163 | accounts.add(kds.read_string()) 164 | # Note: don't need to add otheraccount, because moves are 165 | # always double-entry 166 | 167 | for name in sorted(accounts): 168 | print(name) 169 | 170 | db.close() 171 | 172 | def rewrite_wallet(db_env, destFileName): 173 | db = open_wallet(db_env) 174 | 175 | db_out = DB(db_env) 176 | try: 177 | r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE) 178 | except DBError: 179 | r = True 180 | 181 | if r is not None: 182 | logging.error("Couldn't open %s."%destFileName) 183 | sys.exit(1) 184 | 185 | def item_callback(type, d): 186 | db_out.put(d["__key__"], d["__value__"]) 187 | 188 | parse_wallet(db, item_callback) 189 | 190 | db_out.close() 191 | db.close() 192 | -------------------------------------------------------------------------------- /block.py: -------------------------------------------------------------------------------- 1 | # 2 | # Code for dumping a single block, given its ID (hash) 3 | # 4 | 5 | from bsddb.db import * 6 | import logging 7 | import os.path 8 | import re 9 | import sys 10 | import time 11 | 12 | from BCDataStream import * 13 | from base58 import public_key_to_bc_address 14 | from util import short_hex, long_hex 15 | from deserialize import * 16 | 17 | def _open_blkindex(db_env): 18 | db = DB(db_env) 19 | try: 20 | r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) 21 | except DBError: 22 | r = True 23 | if r is not None: 24 | logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") 25 | sys.exit(1) 26 | return db 27 | 28 | def _read_CDiskTxPos(stream): 29 | n_file = stream.read_uint32() 30 | n_block_pos = stream.read_uint32() 31 | n_tx_pos = stream.read_uint32() 32 | return (n_file, n_block_pos, n_tx_pos) 33 | 34 | def _dump_block(datadir, nFile, nBlockPos, hash256, hashNext, do_print=True): 35 | blockfile = open(os.path.join(datadir, "blk%04d.dat"%(nFile,)), "rb") 36 | ds = BCDataStream() 37 | ds.map_file(blockfile, nBlockPos) 38 | d = parse_Block(ds) 39 | block_string = deserialize_Block(d) 40 | ds.close_file() 41 | blockfile.close() 42 | if do_print: 43 | print "BLOCK "+long_hex(hash256[::-1]) 44 | print "Next block: "+long_hex(hashNext[::-1]) 45 | print block_string 46 | return block_string 47 | 48 | def _parse_block_index(vds): 49 | d = {} 50 | d['version'] = vds.read_int32() 51 | d['hashNext'] = vds.read_bytes(32) 52 | d['nFile'] = vds.read_uint32() 53 | d['nBlockPos'] = vds.read_uint32() 54 | d['nHeight'] = vds.read_int32() 55 | 56 | d['b_version'] = vds.read_int32() 57 | d['hashPrev'] = vds.read_bytes(32) 58 | d['hashMerkle'] = vds.read_bytes(32) 59 | d['nTime'] = vds.read_int32() 60 | d['nBits'] = vds.read_int32() 61 | d['nNonce'] = vds.read_int32() 62 | return d 63 | 64 | def dump_block(datadir, db_env, block_hash): 65 | """ Dump a block, given hexadecimal hash-- either the full hash 66 | OR a short_hex version of the it. 67 | """ 68 | db = _open_blkindex(db_env) 69 | 70 | kds = BCDataStream() 71 | vds = BCDataStream() 72 | 73 | n_blockindex = 0 74 | 75 | key_prefix = "\x0ablockindex" 76 | cursor = db.cursor() 77 | (key, value) = cursor.set_range(key_prefix) 78 | 79 | while key.startswith(key_prefix): 80 | kds.clear(); kds.write(key) 81 | vds.clear(); vds.write(value) 82 | 83 | type = kds.read_string() 84 | hash256 = kds.read_bytes(32) 85 | hash_hex = long_hex(hash256[::-1]) 86 | block_data = _parse_block_index(vds) 87 | 88 | if (hash_hex.startswith(block_hash) or short_hex(hash256[::-1]).startswith(block_hash)): 89 | print "Block height: "+str(block_data['nHeight']) 90 | _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], hash256, block_data['hashNext']) 91 | 92 | (key, value) = cursor.next() 93 | 94 | db.close() 95 | 96 | def read_block(db_cursor, hash): 97 | (key,value) = db_cursor.set_range("\x0ablockindex"+hash) 98 | vds = BCDataStream() 99 | vds.clear(); vds.write(value) 100 | block_data = _parse_block_index(vds) 101 | block_data['hash256'] = hash 102 | return block_data 103 | 104 | def dump_block_n(datadir, db_env, block_number): 105 | """ Dump a block given block number (== height, genesis block is 0) 106 | """ 107 | db = _open_blkindex(db_env) 108 | 109 | kds = BCDataStream() 110 | vds = BCDataStream() 111 | 112 | # Read the hashBestChain record: 113 | cursor = db.cursor() 114 | (key, value) = cursor.set_range("\x0dhashBestChain") 115 | vds.write(value) 116 | hashBestChain = vds.read_bytes(32) 117 | 118 | block_data = read_block(cursor, hashBestChain) 119 | 120 | while block_data['nHeight'] > block_number: 121 | block_data = read_block(cursor, block_data['hashPrev']) 122 | 123 | print "Block height: "+str(block_data['nHeight']) 124 | _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], block_data['hash256'], block_data['hashNext']) 125 | 126 | def search_blocks(datadir, db_env, pattern): 127 | """ Dump a block given block number (== height, genesis block is 0) 128 | """ 129 | db = _open_blkindex(db_env) 130 | kds = BCDataStream() 131 | vds = BCDataStream() 132 | 133 | # Read the hashBestChain record: 134 | cursor = db.cursor() 135 | (key, value) = cursor.set_range("\x0dhashBestChain") 136 | vds.write(value) 137 | hashBestChain = vds.read_bytes(32) 138 | block_data = read_block(cursor, hashBestChain) 139 | 140 | if pattern == "NONSTANDARD_CSCRIPTS": # Hack to look for non-standard transactions 141 | search_odd_scripts(datadir, cursor, block_data) 142 | return 143 | 144 | while True: 145 | block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], 146 | block_data['hash256'], block_data['hashNext'], False) 147 | 148 | if re.search(pattern, block_string) is not None: 149 | print "MATCH: Block height: "+str(block_data['nHeight']) 150 | print block_string 151 | 152 | if block_data['nHeight'] == 0: 153 | break 154 | block_data = read_block(cursor, block_data['hashPrev']) 155 | 156 | def search_odd_scripts(datadir, cursor, block_data): 157 | """ Look for non-standard transactions """ 158 | while True: 159 | block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], 160 | block_data['hash256'], block_data['hashNext'], False) 161 | 162 | import pdb 163 | if block_data['nHeight'] == 71036: 164 | pdb.set_trace() 165 | 166 | found_nonstandard = False 167 | for m in re.finditer(r'TxIn:(.*?)$', block_string, re.MULTILINE): 168 | s = m.group(1) 169 | if re.match(r'\s*COIN GENERATED coinbase:\w+$', s): continue 170 | if re.match(r'.*sig: \d+:\w+...\w+ \d+:\w+...\w+$', s): continue 171 | if re.match(r'.*sig: \d+:\w+...\w+$', s): continue 172 | print "Nonstandard TxIn: "+s 173 | found_nonstandard = True 174 | break 175 | 176 | for m in re.finditer(r'TxOut:(.*?)$', block_string, re.MULTILINE): 177 | s = m.group(1) 178 | if re.match(r'.*Script: DUP HASH160 \d+:\w+...\w+ EQUALVERIFY CHECKSIG$', s): continue 179 | if re.match(r'.*Script: \d+:\w+...\w+ CHECKSIG$', s): continue 180 | print "Nonstandard TxOut: "+s 181 | found_nonstandard = True 182 | break 183 | 184 | if found_nonstandard: 185 | print "NONSTANDARD TXN: Block height: "+str(block_data['nHeight']) 186 | print block_string 187 | 188 | if block_data['nHeight'] == 0: 189 | break 190 | block_data = read_block(cursor, block_data['hashPrev']) 191 | 192 | def check_block_chain(db_env): 193 | """ Make sure hashPrev/hashNext pointers are consistent through block chain """ 194 | db = _open_blkindex(db_env) 195 | 196 | kds = BCDataStream() 197 | vds = BCDataStream() 198 | 199 | # Read the hashBestChain record: 200 | cursor = db.cursor() 201 | (key, value) = cursor.set_range("\x0dhashBestChain") 202 | vds.write(value) 203 | hashBestChain = vds.read_bytes(32) 204 | 205 | back_blocks = [] 206 | 207 | block_data = read_block(cursor, hashBestChain) 208 | 209 | while block_data['nHeight'] > 0: 210 | back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) ) 211 | block_data = read_block(cursor, block_data['hashPrev']) 212 | 213 | back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) ) 214 | genesis_block = block_data 215 | 216 | print("check block chain: genesis block merkle hash is: %s"%(block_data['hashMerkle'][::-1].encode('hex_codec'))) 217 | 218 | while block_data['hashNext'] != ('\0'*32): 219 | forward = (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) 220 | back = back_blocks.pop() 221 | if forward != back: 222 | print("Forward/back block mismatch at height %d!"%(block_data['nHeight'],)) 223 | print(" Forward: "+str(forward)) 224 | print(" Back: "+str(back)) 225 | block_data = read_block(cursor, block_data['hashNext']) 226 | -------------------------------------------------------------------------------- /deserialize.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | from BCDataStream import * 6 | from enumeration import Enumeration 7 | from base58 import public_key_to_bc_address, hash_160_to_bc_address 8 | import socket 9 | import time 10 | from util import short_hex, long_hex 11 | 12 | def parse_CAddress(vds): 13 | d = {} 14 | d['nVersion'] = vds.read_int32() 15 | d['nTime'] = vds.read_uint32() 16 | d['nServices'] = vds.read_uint64() 17 | d['pchReserved'] = vds.read_bytes(12) 18 | d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) 19 | d['port'] = vds.read_uint16() 20 | return d 21 | 22 | def deserialize_CAddress(d): 23 | return d['ip']+":"+str(d['port'])+" (lastseen: %s)"%(time.ctime(d['nTime']),) 24 | 25 | def parse_setting(setting, vds): 26 | if setting[0] == "f": # flag (boolean) settings 27 | return str(vds.read_boolean()) 28 | elif setting[0:4] == "addr": # CAddress 29 | d = parse_CAddress(vds) 30 | return deserialize_CAddress(d) 31 | elif setting == "nTransactionFee": 32 | return vds.read_int64() 33 | elif setting == "nLimitProcessors": 34 | return vds.read_int32() 35 | return 'unknown setting' 36 | 37 | def parse_TxIn(vds): 38 | d = {} 39 | d['prevout_hash'] = vds.read_bytes(32) 40 | d['prevout_n'] = vds.read_uint32() 41 | d['scriptSig'] = vds.read_bytes(vds.read_compact_size()) 42 | d['sequence'] = vds.read_uint32() 43 | return d 44 | def deserialize_TxIn(d): 45 | if d['prevout_hash'] == "\x00"*32: 46 | result = "TxIn: COIN GENERATED" 47 | result += " coinbase:"+d['scriptSig'].encode('hex_codec') 48 | else: 49 | result = "TxIn: prev("+long_hex(d['prevout_hash'][::-1])+":"+str(d['prevout_n'])+")" 50 | pk = extract_public_key(d['scriptSig']) 51 | result += " pubkey: "+pk 52 | result += " sig: "+decode_script(d['scriptSig']) 53 | if d['sequence'] < 0xffffffff: result += " sequence: "+hex(d['sequence']) 54 | return result 55 | 56 | def parse_TxOut(vds): 57 | d = {} 58 | d['value'] = vds.read_int64() 59 | d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size()) 60 | return d 61 | def deserialize_TxOut(d): 62 | result = "TxOut: value: %.2f"%(d['value']/1.0e8,) 63 | pk = extract_public_key(d['scriptPubKey']) 64 | result += " pubkey: "+pk 65 | result += " Script: "+decode_script(d['scriptPubKey']) 66 | return result 67 | 68 | def parse_Transaction(vds): 69 | d = {} 70 | d['version'] = vds.read_int32() 71 | n_vin = vds.read_compact_size() 72 | d['txIn'] = [] 73 | for i in xrange(n_vin): 74 | d['txIn'].append(parse_TxIn(vds)) 75 | n_vout = vds.read_compact_size() 76 | d['txOut'] = [] 77 | for i in xrange(n_vout): 78 | d['txOut'].append(parse_TxOut(vds)) 79 | d['lockTime'] = vds.read_uint32() 80 | return d 81 | def deserialize_Transaction(d): 82 | result = "%d tx in, %d out\n"%(len(d['txIn']), len(d['txOut'])) 83 | for txIn in d['txIn']: 84 | result += deserialize_TxIn(txIn) + "\n" 85 | for txOut in d['txOut']: 86 | result += deserialize_TxOut(txOut) + "\n" 87 | return result 88 | 89 | def parse_MerkleTx(vds): 90 | d = parse_Transaction(vds) 91 | d['hashBlock'] = vds.read_bytes(32) 92 | n_merkleBranch = vds.read_compact_size() 93 | d['merkleBranch'] = vds.read_bytes(32*n_merkleBranch) 94 | d['nIndex'] = vds.read_int32() 95 | return d 96 | 97 | def deserialize_MerkleTx(d): 98 | result = deserialize_Transaction(d) 99 | result = "Merkle hashBlock: "+short_hex(d['hashBlock'][::-1])+"\n" + result 100 | return result 101 | 102 | def parse_WalletTx(vds): 103 | d = parse_MerkleTx(vds) 104 | n_vtxPrev = vds.read_compact_size() 105 | d['vtxPrev'] = [] 106 | for i in xrange(n_vtxPrev): 107 | d['vtxPrev'].append(parse_MerkleTx(vds)) 108 | 109 | d['mapValue'] = {} 110 | n_mapValue = vds.read_compact_size() 111 | for i in xrange(n_mapValue): 112 | key = vds.read_string() 113 | value = vds.read_string() 114 | d['mapValue'][key] = value 115 | n_orderForm = vds.read_compact_size() 116 | d['orderForm'] = [] 117 | for i in xrange(n_orderForm): 118 | first = vds.read_string() 119 | second = vds.read_string() 120 | d['orderForm'].append( (first, second) ) 121 | # Versioning was messed up before bitcoin 0.3.14.04; 122 | # nVersion is actually fTimeReceivedIsTxTime before then. 123 | d['nVersion'] = vds.read_uint32() 124 | d['timeReceived'] = vds.read_uint32() 125 | d['fromMe'] = vds.read_boolean() 126 | d['spent'] = vds.read_boolean() 127 | if d['nVersion'] > 31404: 128 | d['fTimeReceivedIsTxTime'] = vds.read_boolean() 129 | d['fUnused'] = vds.read_boolean() 130 | d['fromAccount'] = vds.read_string() 131 | 132 | return d 133 | 134 | def deserialize_WalletTx(d): 135 | result = deserialize_MerkleTx(d) 136 | 137 | result += "mapValue:"+str(d['mapValue']) 138 | # One of these days I'll ask Satoshi what the orderForm stuff is/was for... 139 | # result += "\n"+" orderForm:"+str(d['orderForm']) 140 | result += "\n"+"timeReceived:"+time.ctime(d['timeReceived'])+" fromMe:"+str(d['fromMe'])+" spent:"+str(d['spent']) 141 | if d['nVersion'] > 31404: 142 | result += "\n fromAccount: "+d['fromAccount'] 143 | return result 144 | 145 | def parse_Block(vds): 146 | d = {} 147 | d['version'] = vds.read_int32() 148 | d['hashPrev'] = vds.read_bytes(32) 149 | d['hashMerkleRoot'] = vds.read_bytes(32) 150 | d['nTime'] = vds.read_uint32() 151 | d['nBits'] = vds.read_uint32() 152 | d['nNonce'] = vds.read_uint32() 153 | d['transactions'] = [] 154 | nTransactions = vds.read_compact_size() 155 | for i in xrange(nTransactions): 156 | d['transactions'].append(parse_Transaction(vds)) 157 | 158 | return d 159 | 160 | def deserialize_Block(d): 161 | result = "Time: "+time.ctime(d['nTime'])+" Nonce: "+str(d['nNonce']) 162 | result += "\nnBits: 0x"+hex(d['nBits']) 163 | result += "\nhashMerkleRoot: 0x"+d['hashMerkleRoot'][::-1].encode('hex_codec') 164 | result += "\nPrevious block: "+d['hashPrev'][::-1].encode('hex_codec') 165 | result += "\n%d transactions:\n"%len(d['transactions']) 166 | for t in d['transactions']: 167 | result += deserialize_Transaction(t)+"\n" 168 | return result 169 | 170 | opcodes = Enumeration("Opcodes", [ 171 | ("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED", 172 | "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7", 173 | "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16", 174 | "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY", 175 | "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP", 176 | "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT", 177 | "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", "OP_AND", 178 | "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL", 179 | "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV", 180 | "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR", 181 | "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN", 182 | "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX", 183 | "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", 184 | "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", 185 | "OP_CHECKMULTISIGVERIFY", 186 | ("OP_SINGLEBYTE_END", 0xF0), 187 | ("OP_DOUBLEBYTE_BEGIN", 0xF000), 188 | "OP_PUBKEY", "OP_PUBKEYHASH", 189 | ("OP_INVALIDOPCODE", 0xFFFF), 190 | ]) 191 | 192 | def script_GetOp(bytes): 193 | i = 0 194 | while i < len(bytes): 195 | vch = None 196 | opcode = ord(bytes[i]) 197 | i += 1 198 | if opcode >= opcodes.OP_SINGLEBYTE_END: 199 | opcode <<= 8 200 | opcode |= bytes[i] 201 | i += 1 202 | 203 | if opcode <= opcodes.OP_PUSHDATA4: 204 | nSize = opcode 205 | if opcode == opcodes.OP_PUSHDATA1: 206 | nSize = ord(bytes[i]) 207 | i += 1 208 | elif opcode == opcodes.OP_PUSHDATA2: 209 | nSize = unpack_from(' 0: result += " " 226 | if opcode <= opcodes.OP_PUSHDATA4: 227 | result += "%d:"%(opcode,) 228 | result += short_hex(vch) 229 | else: 230 | result += script_GetOpName(opcode) 231 | return result 232 | 233 | def match_decoded(decoded, to_match): 234 | if len(decoded) != len(to_match): 235 | return False; 236 | for i in range(len(decoded)): 237 | if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4: 238 | continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent. 239 | if to_match[i] != decoded[i][0]: 240 | return False 241 | return True 242 | 243 | def extract_public_key(bytes): 244 | decoded = [ x for x in script_GetOp(bytes) ] 245 | 246 | # non-generated TxIn transactions push a signature 247 | # (seventy-something bytes) and then their public key 248 | # (65 bytes) onto the stack: 249 | match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] 250 | if match_decoded(decoded, match): 251 | return public_key_to_bc_address(decoded[1][1]) 252 | 253 | # The Genesis Block, self-payments, and pay-by-IP-address payments look like: 254 | # 65 BYTES:... CHECKSIG 255 | match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ] 256 | if match_decoded(decoded, match): 257 | return public_key_to_bc_address(decoded[0][1]) 258 | 259 | # Pay-by-Bitcoin-address TxOuts look like: 260 | # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG 261 | match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] 262 | if match_decoded(decoded, match): 263 | return hash_160_to_bc_address(decoded[2][1]) 264 | 265 | return "(None)" 266 | -------------------------------------------------------------------------------- /NOTES.txt: -------------------------------------------------------------------------------- 1 | Transaction dissection notes: 2 | ----------------------------- 3 | 4 | My first two transactions look like this: 5 | 6 | ==WalletTransaction== f240...2582 7 | Merkle hashBlock: 6d40...0000 8 | 1 tx in, 1 out 9 | ['TxIn: prev(e9d4...d2f4:0) pubkey: 1Lfic7L3z7xUbxRDvMNFiE3QxKRmSSrvEH sig: 72:3045...1a01 65:0403...7b86'] 10 | ['TxOut: value: 500.00 pubkey: 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 Script: DUP HASH160 20:61e4...614c EQUALVERIFY CHECKSIG'] 11 | timeReceived:Thu May 27 14:25:48 2010 fromMe:True spent:True 12 | 13 | ==WalletTransaction== f961...4472 14 | Merkle hashBlock: 7707...0000 15 | 1 tx in, 2 out 16 | ['TxIn: prev(f240...2582:0) pubkey: 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 sig: 72:3045...a801 65:04a6...a541'] 17 | ['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'] 18 | timeReceived:Fri May 28 13:55:23 2010 fromMe:True spent:True 19 | 20 | So: whoever has sending bc address 1Lfic7L3z7xUbxRDvMNFiE3QxKRmSSrvEH sent 500 bitcoins to 21 | my 19vcWM6EEbQHVdN2W8NXv9ySgsPjbZ6gU3 receiving address. 22 | 23 | I then sent 142.40 to bcaddress 15F9miF9egehbeCiPKVSJSwgw5J96my7Rf. 24 | And got 357.60 in change (Bitcoin created a new address for the change: 1Av6tJSFv3bZvUfaALm4TQZmYvdeuHcbUb) 25 | 26 | Spending generated coins looks different: 27 | 28 | Transaction 6403...cc0e 29 | ['TxIn: COIN GENERATED coinbase:0464ba0e1c010a'] 30 | ['TxOut: value: 50.00 pubkey: 1Cpr8WJYa5igdo8AtPS24hfVLkk6ANZSWN Script: 65:0442...11c2 CHECKSIG'] 31 | 32 | Transaction db39...8dcd: 33 | ['TxIn: COIN GENERATED coinbase:0464ba0e1c0130'] 34 | ['TxOut: value: 50.00 pubkey: 1LuamEE1rL1QAdgn48gGFD9T9mnqSeP17w Script: 65:04f2...e10f CHECKSIG'] 35 | 36 | ==WalletTransaction== 3860...f888 37 | ['TxIn: prev(6403...cc0e:0) pubkey: (None) sig: 72:3045...f501', 'TxIn: prev(db39...8dcd:0) pubkey: (None) sig: 73:3046...9\ 38 | 601'] 39 | ['TxOut: value: 100.00 pubkey: 1BBWNxMqU2bTwSVeUDVKmNYJrWr4D1pBrf Script: DUP HASH160 20:6fad...ab90 EQUALVERIFY CHECKSIG'] 40 | 41 | Here I received 100 bitcoins from two 50 COIN GENERATED transactions. 42 | 43 | 44 | blkindex.dat serialization notes: 45 | --------------------------------- 46 | 47 | Keys beging with a serialized string, rest of key and value depend on that first string. Possibilities are: 48 | 49 | tx : key continues with uint256 transaction hash/id (32 bytes) 50 | value is: 51 | version : 32-bit unsigned int 52 | CDiskTxPos : location of transaction (in the blk000N.dat files) 53 | vector : location of transactions that spend this transaction's inputs 54 | 55 | blockindex : key continues with uint256 block hash (32 bytes) 56 | value is serialized CDiskBlockIndex 57 | 58 | version : value is integer version number 59 | 60 | hashBestChain : value is 32-byte SHA256 hash of last block in the longest block chain. 61 | 62 | 63 | wallet.dat serialization notes: 64 | ------------------------------- 65 | Berkely DB BTREE file (key-value pairs). 66 | 67 | Keys begin with a serialized string, rest of key and value depend on that first string. Possibilities are: 68 | 69 | name : key continues with 34-character Bitcoin "hash160" address 70 | value is a simple string. 71 | 72 | tx : key continues with uint256 (32 bytes) 73 | value is a serialized CTransaction+CMerkleTx+CWalletTx (see below for details) 74 | 75 | key : key continues with vector that is public key 76 | value is vector that is private key 77 | 78 | wkey : key continues with vector that is public key 79 | value is serialized CWalletKey: 80 | vector that is private key 81 | int64 nTimeCreated 82 | int64 nTimeExpires 83 | string comment 84 | 85 | defaultkey : 86 | value is vector default key 87 | 88 | version : 89 | value is int version number 90 | 91 | setting : key continues with setting name, value depends on setting as follows: 92 | addrIncoming : CAddress serialized 93 | addrProxy : CAddress serialized 94 | fGenerateBitcoins : 1-character (boolean) 95 | fLimitProcessors : 1-character (boolean) 96 | fMinimizeOnClose : 1-character (boolean) 97 | fMinimizeToTray : 1-character (boolean) 98 | fUseProxy : 1-character (boolean) 99 | nLimitProcessors : int 100 | nTransactionFee : int64 101 | 102 | 103 | Complex value type serialization: 104 | --------------------------------- 105 | 106 | CDiskTxPos: 107 | uint32 nFile : Which of the blk000*.dat files 108 | uint32 nBlockPos : Byte position in file 109 | uint32 nTxPos : Byte position in file 110 | 111 | CDiskBlockIndex: 112 | int nVersion 113 | uint256 hashNext 114 | uint32 nFile : Which of the blk000*.dat files 115 | uint32 nBlockPos : Byte position in file 116 | int nHeight : height in block chain 117 | int blockVersion : ??? Not sure why version is repeated... 118 | uint256 hashPrev 119 | uint256 hashMerkleRoot 120 | uint32 nTime 121 | uint32 nBits 122 | uint32 nNonce 123 | 124 | CBlock: 125 | # Note: 4 bytes: f9 be b4 d9 are written before records in blk000N.dat files 126 | # But the nBlockPos pointers in CDiskBlockIndex points to start of serializedSize 127 | int serializedSize 128 | int version 129 | uint256 hashPrev 130 | uint256 hashMerkleRoot 131 | uint32 nTime 132 | uint32 nBits 133 | uint32 nNonce 134 | vector 135 | 136 | CAddress: 137 | int nVersion 138 | unsigned int nTime 139 | uint64 nServices 140 | unsigned char[12] pchReserved 141 | unsigned int ip 142 | unsigned short port 143 | 144 | CTransaction: 145 | int nVersion 146 | vector vin 147 | vector vout 148 | unsigned int nLockTime 149 | 150 | CTxIn: 151 | COutPoint prevout 152 | CScript scriptSig 153 | unsigned int nSequence 154 | 155 | CTxOut: 156 | int64 nValue 157 | CScript scriptPubKey 158 | 159 | COutPoint: 160 | 36 bytes(FLATDATA): 32-byte hash, 4-byte unsigned int 161 | 162 | CScript: 163 | vector containing built-in little scripting-language script 164 | (opcodes and arguments to do crypto stuff) 165 | 166 | CMerkleTx: 167 | ... serialize CTransaction, then: 168 | uint256 hashBlock 169 | vector vMerkleBranch 170 | int nIndex 171 | 172 | CWalletTx: 173 | ... serialized CMerkleTx, then: 174 | vector vtxPrev 175 | map mapValue 176 | vector > vOrderForm 177 | unsigned int fTimeReceivedIsTxTime 178 | unsigned int nTimeReceived 179 | char fFromMe 180 | char fSpent 181 | 182 | CInv: 183 | int type : 1:tx 2:block 184 | uint256 hash 185 | 186 | CBlockLocator: 187 | int nVersion 188 | vector # Block hashes, newest back to genesis block (dense to start, but then sparse) 189 | 190 | string: 191 | 1/3/5/9 bytes giving length: 192 | 1 byte if length < 253. 193 | otherwise byte value '253'+ushort, '254'+uint, '255'+uint64 194 | then length bytes. 195 | 196 | vector: 197 | 1/3/5/9 bytes giving count (see string, above) 198 | followed by that many serialized one-after-another 199 | 200 | pair: 201 | just first item followed by second item 202 | 203 | 204 | PUBLIC KEYS TO BITCOIN ADDRESSES 205 | -------------------------------- 206 | 207 | Public key, in memory (65 bytes): 208 | 209 | 0x94c7818: 0x04 0x57 0xcc 0xad 0xd7 0x1e 0xb0 0xf3 210 | 0x94c7820: 0xc1 0x9d 0x22 0xb9 0xba 0x0e 0xa1 0xf3 211 | 0x94c7828: 0x44 0x2a 0x6f 0x12 0x31 0x46 0xb5 0xbd 212 | 0x94c7830: 0xff 0x10 0x60 0xbc 0xd1 0x11 0x68 0xe6 213 | 0x94c7838: 0x6a 0x71 0xbe 0xd4 0xda 0x17 0x7c 0x12 214 | 0x94c7840: 0xd7 0x30 0x9a 0xdd 0xfd 0xf5 0x6c 0x31 215 | 0x94c7848: 0xd5 0xc8 0xa2 0x7b 0x8e 0x6a 0x22 0x20 216 | 0x94c7850: 0x38 0x42 0xc6 0xc2 0x4f 0xd5 0x9b 0xd7 217 | 0x94c7858: 0xb7 218 | Python string: 219 | 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" 220 | 221 | SHA256 hash of that is: 222 | 0xb6890938: 0x0d 0x72 0xab 0x02 0xc8 0xab 0x52 0xce 223 | 0xb6890940: 0x7e 0x6b 0x04 0x00 0x95 0x58 0x09 0xf0 224 | 0xb6890948: 0x93 0x48 0x21 0xb6 0x26 0xc3 0x27 0xc7 225 | 0xb6890950: 0x9a 0x07 0x62 0xfd 0xbc 0x5e 0xb8 0xa5 226 | h1 = SHA256.new(public_key).digest() 227 | 228 | RIPEMD160(SHA256(public key)) is: 229 | 0xb68909ac: 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 0xa2 230 | 0xb68909b4: 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 0x67 231 | 0xb68909bc: 0x28 0x26 0x30 0xd3 232 | h2 = RIPEMD160.new(h1).digest() 233 | 234 | Put version number '0x00' byte onto front: 235 | 0x9eeb840: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 236 | 0x9eeb848: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 237 | 0x9eeb850: 0x67 0x28 0x26 0x30 0xd3 238 | vh2 = "\x00"+h2 239 | 240 | Hash (double-SHA256) that: 241 | 0xb68908e8: 0xf9 0xb7 0x8e 0x64 0x6e 0x20 0x27 0xc4 242 | 0xb68908f0: 0xaa 0x62 0x66 0x04 0x2e 0xb6 0xa2 0xe0 243 | 0xb68908f8: 0x41 0x03 0x9d 0xd8 0xe2 0x24 0x24 0xe8 244 | 0xb6890900: 0x50 0xac 0x20 0x29 0xfb 0xcd 0xb4 0x6e 245 | h3=SHA256.new(SHA256.new(vh2).digest()).digest() 246 | 247 | Add first 4 bytes from Hash object onto end as check-bytes: 248 | 0x9fa6628: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 249 | 0x9fa6630: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 250 | 0x9fa6638: 0x67 0x28 0x26 0x30 0xd3 0xf9 0xb7 0x8e 251 | 0x9fa6640: 0x64 252 | addr=vh2+h3[0:4] 253 | 254 | Result length should be: int(math.floor(len(addr)*8/math.log(58,2))) 255 | Base58 encode that, front-pad with '1's if less than expected length 256 | to get: 19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT 257 | 258 | 259 | WIRE PROTOCOL NOTES 260 | ------------------- 261 | Default port is 8333 262 | 263 | // Message format 264 | // (4) message start { 0xf9, 0xbe, 0xb4, 0xd9 } 265 | // (12) command 266 | // (4) size -- number of bytes of data 267 | // (4) checksum -- First four bytes of double SHA256 hash of data 268 | // (x) data 269 | 270 | --> messages might be split by network layer... 271 | 272 | Commands are: 273 | 274 | "version" : 275 | int nVersion 276 | uint64 nServices 277 | int64 nTime 278 | CAddress addrMe 279 | CAddress addrFrom # if nVersion > 106 280 | uint64 nNonce 281 | string strSubVer # if nVersion > 106 282 | int nStartingHeight # if nVersion > 209 283 | 284 | nNonce is random value (I think), used to detect self-connection. 285 | 286 | "verack" : no data. Sent to sync min version (peer, peer) 287 | 288 | "addr" : 289 | vector 290 | (relayed to 10 random nodes so they spread) 291 | 292 | "inv" : 293 | vector 294 | 295 | "getdata" : Asks for blocks or tx's (response is "block" or "tx" messages) 296 | vector 297 | 298 | "getblocks" : 299 | CBLockLocator 300 | uint256 hashStop 301 | 302 | "tx" : 303 | CTransaction 304 | 305 | "block" : 306 | CBlock 307 | 308 | "getaddr" : no data ("please send me addr messages") 309 | 310 | "checkorder" 311 | uint256 hashReply 312 | CWalletTx order 313 | 314 | "submitorder" 315 | uint256 hashReply 316 | CWalletTx wtxNew 317 | 318 | "reply" 319 | uint256 hashReply 320 | 321 | "ping" : no data (and no reply) 322 | 323 | 324 | TRANSACTION SIGNING NOTES 325 | ------------------------- 326 | 327 | So, I want to spend some bitcoin. 328 | First, I gotta have a Transaction to me-- specifically, a Transaction with a TxOut that contains a scriptPubKey that I can satisfy. 329 | 330 | TxOut.scriptPubKey's come in a couple different flavors: 331 | DUP HASH160 {hash160(public_key) EQUALVERIFY CHECKSIG 332 | --> I have to be able to supply a matching TxIn.scriptSig with: signature and public key 333 | (public key) CHECKSIG 334 | --> I have to supply a matching TxIn.scriptSig with: signature 335 | 336 | TODO: figure out what, exactly, is hashed (and how), and how, exactly, that hash value is signed with the private key. 337 | 338 | DIFFICULTY, NBITS, BN_mpi2bn 339 | ---------------------------- 340 | 341 | Hash targets are stored as "compact bignums;" the production block chain has an initial target of: 342 | 0x1d00ffff ... which means "create a bignum that is 0x1d (29) bytes long and has 0x00ffff as the 343 | three bytes on the big end." Or: 0xffff0000000000000000000000000000000000000000000000000000 344 | 345 | As I write this, the current block->nBits is 0x1c010c5. To compute difficulty: 346 | php -r '$nBits = 0x1c010c5a; print( (0xffff << ((0x1d-($nBits>>24))*8)) / ($nBits&0xffffff) );' 347 | 348 | 349 | --------------------------------------------------------------------------------