diff --git a/README b/README index f950d5d..bda9553 100644 --- a/README +++ b/README @@ -1,12 +1,12 @@ -pywallet.py 1.0 - -based on http://github.com/gavinandresen/bitcointools - -Usage: pywallet.py [options] - -Options: - --version show program's version number and exit - -h, --help show this help message and exit - --dumpwallet dump wallet in json format - --importprivkey=KEY import private key from vanitygen - --datadir=DATADIR wallet directory (defaults to bitcoin default) +pywallet.py 1.0 + +based on http://github.com/gavinandresen/bitcointools + +Usage: pywallet.py [options] + +Options: + --version show program's version number and exit + -h, --help show this help message and exit + --dumpwallet dump wallet in json format + --importprivkey=KEY import private key from vanitygen + --datadir=DATADIR wallet directory (defaults to bitcoin default) diff --git a/pywallet.py b/pywallet.py index 0c0fad1..e20f50f 100644 --- a/pywallet.py +++ b/pywallet.py @@ -1,708 +1,698 @@ #!/usr/bin/env python # -# pywallet.py 1.0 +# pywallet.py 1.1 # -# based on http://github.com/gavinandresen/bitcointools -# -# Usage: pywallet.py [options] -# -# Options: -# --version show program's version number and exit -# -h, --help show this help message and exit -# --dumpwallet dump wallet in json format -# --importprivkey=KEY import private key from vanitygen -# --datadir=DATADIR wallet directory (defaults to bitcoin default) - -from bsddb.db import * -import os, sys, time -import json -import logging -import struct -import StringIO -import traceback -import socket -import types -import string -import exceptions -import hashlib -from ctypes import * - -TESTNET = 0 - -json_db = {} -private_keys = [] - -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") - -dlls = list() -if 'win' in sys.platform: - for d in ('libeay32.dll', 'libssl32.dll', 'ssleay32.dll'): - try: - dlls.append( cdll.LoadLibrary(d) ) - except: - pass -else: - dlls.append( cdll.LoadLibrary('libssl.so') ) - -class BIGNUM_Struct (Structure): - _fields_ = [("d",c_void_p),("top",c_int),("dmax",c_int),("neg",c_int),("flags",c_int)] - -class BN_CTX_Struct (Structure): - _fields_ = [ ("_", c_byte) ] - -BIGNUM = POINTER( BIGNUM_Struct ) -BN_CTX = POINTER( BN_CTX_Struct ) - -def load_func( name, args, returns = c_int): - d = sys.modules[ __name__ ].__dict__ - f = None - - for dll in dlls: - try: - f = getattr(dll, name) - f.argtypes = args - f.restype = returns - d[ name ] = f - return - except: - pass - raise ImportError('Unable to load required functions from SSL dlls') - -load_func( 'BN_new', [], BIGNUM ) -load_func( 'BN_CTX_new', [], BN_CTX ) -load_func( 'BN_CTX_free', [BN_CTX], None ) -load_func( 'BN_num_bits', [BIGNUM], c_int ) -load_func( 'BN_bn2bin', [BIGNUM, c_char_p] ) -load_func( 'BN_bin2bn', [c_char_p, c_int, BIGNUM], BIGNUM ) -load_func( 'EC_KEY_new_by_curve_name', [c_int], c_void_p ) -load_func( 'EC_KEY_get0_group', [c_void_p], c_void_p) -load_func( 'EC_KEY_get0_private_key', [c_void_p], BIGNUM) -load_func( 'EC_POINT_new', [c_void_p], c_void_p) -load_func( 'EC_POINT_free', [c_void_p]) -load_func( 'EC_POINT_mul', [c_void_p, c_void_p, BIGNUM, c_void_p, BIGNUM, BN_CTX], c_int) -load_func( 'EC_KEY_set_private_key', [c_void_p, BIGNUM], c_void_p) -load_func( 'EC_KEY_set_public_key', [c_void_p, c_void_p], c_void_p) -load_func( 'i2d_ECPrivateKey', [ c_void_p, POINTER(POINTER(c_char))], c_int ) -load_func( 'i2o_ECPublicKey', [ c_void_p, POINTER(POINTER(c_char))], c_int ) - -def BN_num_bytes(a): - return ((BN_num_bits(a)+7)/8) - -NID_secp256k1 = 714 - -pkey = 0 - -def EC_KEY_regenerate_key(eckey, priv_key): - group = EC_KEY_get0_group(eckey) - ctx = BN_CTX_new() - pub_key = EC_POINT_new(group) - EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) - EC_KEY_set_private_key(eckey, priv_key) - EC_KEY_set_public_key(eckey, pub_key) - EC_POINT_free(pub_key) - BN_CTX_free(ctx) - -def GetSecret(pkey): - bn = EC_KEY_get0_private_key(pkey) - nSize = BN_num_bytes(bn) - b = create_string_buffer(nSize) - BN_bn2bin(bn, b) - return b.raw - -def GetPrivKey(pkey): - nSize = i2d_ECPrivateKey(pkey, None) - p = create_string_buffer(nSize) - i2d_ECPrivateKey(pkey, byref(cast(p, POINTER(c_char)))) - return p.raw - -def GetPubKey(pkey): - nSize = i2o_ECPublicKey(pkey, None) - p = create_string_buffer(nSize) - i2o_ECPublicKey(pkey, byref(cast(p, POINTER(c_char)))) - return p.raw - -def Hash(data): - s1 = hashlib.sha256() - s1.update(data) - h1 = s1.digest() - s2 = hashlib.sha256() - s2.update(h1) - h2 = s2.digest() - return h2 - -def EncodeBase58Check(vchIn): - hash = Hash(vchIn) - return b58encode(vchIn + hash[0:4]) - -def DecodeBase58Check(psz): - vchRet = b58decode(psz, None) - key = vchRet[0:-4] - csum = vchRet[-4:] - hash = Hash(key) - cs32 = hash[0:4] - if cs32 != csum: - return None - else: - return key - -def SecretToASecret(privkey): - vchSecret = privkey[9:9+32] - # add 1-byte version number - vchIn = "\x80" + vchSecret - return EncodeBase58Check(vchIn) - -def ASecretToSecret(key): - vch = DecodeBase58Check(key) - if vch: - return vch[1:] - else: - return False - -def importprivkey(db, key): - - vchSecret = ASecretToSecret(key) - - if not vchSecret: - return False - - pkey = EC_KEY_new_by_curve_name(NID_secp256k1) - bn = BN_bin2bn(vchSecret, 32, BN_new()) - EC_KEY_regenerate_key(pkey, bn) - - secret = GetSecret(pkey) - private_key = GetPrivKey(pkey) - public_key = GetPubKey(pkey) - addr = public_key_to_bc_address(public_key) - - print "Address: %s" % addr - print "Privkey: %s" % SecretToASecret(private_key) - - update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key }) - update_wallet(db, 'name', { 'hash' : addr, 'name' : '' }) - - return True - -__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 - -def hash_160(public_key): - s1 = hashlib.sha256() - s1.update(public_key) - h1 = s1.digest() - s2 = hashlib.new('ripemd160') - s2.update(h1) - h2 = s2.digest() - return h2 - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160): - - if TESTNET: - vh160 = "\x6f" + h160 # \x6f is testnet - else: - vh160 = "\x00" + h160 # \x00 is version 0 - - h3 = Hash(vh160) - addr = vh160 + h3[0:4] - return b58encode(addr) - -def bc_address_to_hash_160(addr): - bytes = b58decode(addr, 25) - return bytes[1:21] - -def long_hex(bytes): - return bytes.encode('hex_codec') - -def short_hex(bytes): - t = bytes.encode('hex_codec') - if len(t) < 32: - return t - return t[0:32]+"..."+t[-32:] - -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 - -def parse_CAddress(vds): - - d = {} - - d['nVersion'] = vds.read_int32() - if d['nVersion'] > 32400: - d['ip'] = '0.0.0.0' - d['port'] = 0 - return d - - 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[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' - -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('= __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 + +def hash_160(public_key): + s1 = hashlib.sha256() + s1.update(public_key) + h1 = s1.digest() + s2 = hashlib.new('ripemd160') + s2.update(h1) + h2 = s2.digest() + return h2 + +def public_key_to_bc_address(public_key): + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160) + +def hash_160_to_bc_address(h160): + vh160 = chr(addrtype) + h160 + h3 = Hash(vh160) + addr = vh160 + h3[0:4] + return b58encode(addr) + +def bc_address_to_hash_160(addr): + bytes = b58decode(addr, 25) + return bytes[1:21] + +def long_hex(bytes): + return bytes.encode('hex_codec') + +def short_hex(bytes): + t = bytes.encode('hex_codec') + if len(t) < 32: + return t + return t[0:32]+"..."+t[-32:] + +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 + +def parse_CAddress(vds): + d = {'ip':'0.0.0.0','port':0,'ntime': 0} + try: + 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() + except: + pass + 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[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' + +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('