X-Git-Url: https://git.novaco.in/?p=electrum-server.git;a=blobdiff_plain;f=backends%2Fbitcoind%2Fdeserialize.py;h=93ad4d42479a0d6301fcb2013884f0acc807c610;hp=21fd9ce785900edb07c45209263a6fbe12ff587b;hb=cbcd7ba2e6a8cf6b64b032f974445f107aff8ad1;hpb=f865c7c4a2c69fbe78aa0c0a66aa3cd743f2d183 diff --git a/backends/bitcoind/deserialize.py b/backends/bitcoind/deserialize.py index 21fd9ce..93ad4d4 100644 --- a/backends/bitcoind/deserialize.py +++ b/backends/bitcoind/deserialize.py @@ -2,228 +2,183 @@ # # -#from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode -#import socket -import time, hashlib +import mmap +import string import struct -addrtype = 0 - - -Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest() -hash_encode = lambda x: x[::-1].encode('hex') -hash_decode = lambda x: x.decode('hex')[::-1] - -def hash_160(public_key): - md = hashlib.new('ripemd160') - md.update(hashlib.sha256(public_key).digest()) - return md.digest() - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) +import types -def hash_160_to_bc_address(h160): - vh160 = chr(addrtype) + h160 - h = Hash(vh160) - addr = vh160 + h[0:4] - return b58encode(addr) +from utils import * -__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 - - -# -# 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 """ + """Thrown when there's a problem deserializing or serializing.""" + class BCDataStream(object): - def __init__(self): - self.input = None - self.read_cursor = 0 + """Workalike python implementation of Bitcoin's CDataStream class.""" + def __init__(self): + self.input = None + self.read_cursor = 0 - def clear(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('= opcodes.OP_SINGLEBYTE_END: - opcode <<= 8 - opcode |= ord(bytes[i]) - i += 1 - - if opcode <= opcodes.OP_PUSHDATA4: - nSize = opcode - if opcode == opcodes.OP_PUSHDATA1: - nSize = ord(bytes[i]) + i = 0 + while i < len(bytes): + vch = None + opcode = ord(bytes[i]) i += 1 - elif opcode == opcodes.OP_PUSHDATA2: - (nSize,) = struct.unpack_from(' len(bytes): + vch = "_INVALID_"+bytes[i:] + i = len(bytes) + else: + vch = bytes[i:i+nSize] + i += nSize + + yield (opcode, vch, i) + def script_GetOpName(opcode): - return (opcodes.whatis(opcode)).replace("OP_", "") + try: + return (opcodes.whatis(opcode)).replace("OP_", "") + except KeyError: + return "InvalidOp_"+str(opcode) + def decode_script(bytes): - result = '' - for (opcode, vch, i) 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 + result = '' + for (opcode, vch, i) 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]) - - # coins sent to black hole - # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG - match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_0, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] - if match_decoded(decoded, match): - return "None" - - # 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]) - - # strange tx - match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_NOP ] - if match_decoded(decoded, match): - return hash_160_to_bc_address(decoded[2][1]) - - #raise BaseException("address not found in script") see ce35795fb64c268a52324b884793b3165233b1e6d678ccaadf760628ec34d76b - return "None" + 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 get_address_from_input_script(bytes): + try: + decoded = [ x for x in script_GetOp(bytes) ] + except: + # coinbase transactions raise an exception + return [], [], None + + # non-generated TxIn transactions push a signature + # (seventy-something bytes) and then their public key + # (33 or 65 bytes) onto the stack: + + match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] + if match_decoded(decoded, match): + return None, None, public_key_to_bc_address(decoded[1][1]) + + # p2sh transaction, 2 of n + match = [ opcodes.OP_0 ] + while len(match) < len(decoded): + match.append(opcodes.OP_PUSHDATA4) + + if match_decoded(decoded, match): + + redeemScript = decoded[-1][1] + num = len(match) - 2 + signatures = map(lambda x:x[1].encode('hex'), decoded[1:-1]) + dec2 = [ x for x in script_GetOp(redeemScript) ] + + # 2 of 2 + match2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec2, match2): + pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ] + return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5) + + # 2 of 3 + match2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec2, match2): + pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] + return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5) + + return [], [], None + + +def get_address_from_output_script(bytes): + try: + decoded = [ x for x in script_GetOp(bytes) ] + except: + return None + + # 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]) + + # coins sent to black hole + # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG + match = [opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_0, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG] + if match_decoded(decoded, match): + return None + + # 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]) + + # strange tx + match = [opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_NOP] + if match_decoded(decoded, match): + return hash_160_to_bc_address(decoded[2][1]) + + # p2sh + match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] + if match_decoded(decoded, match): + addr = hash_160_to_bc_address(decoded[1][1],5) + return addr + + return None