X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=p2pool%2Fbitcoin%2Fdata.py;h=74cb94bb7bc51986c4a9dd6ddda9fb0b862e1aa6;hb=f732111a6e08d7d0649c330d1c703535a8ea80b5;hp=8f9e06a8597284060f11a4b223828ac1d44a7522;hpb=aaf667c4426fdb1e9dd26bcc3e078963eb84d231;p=p2pool.git diff --git a/p2pool/bitcoin/data.py b/p2pool/bitcoin/data.py index 8f9e06a..74cb94b 100644 --- a/p2pool/bitcoin/data.py +++ b/p2pool/bitcoin/data.py @@ -1,330 +1,51 @@ from __future__ import division -import binascii import hashlib -import struct +import random +import warnings -from . import base58 -from p2pool.util import bases, math import p2pool +from p2pool.util import math, pack -class EarlyEnd(Exception): - pass +def hash256(data): + return pack.IntType(256).unpack(hashlib.sha256(hashlib.sha256(data).digest()).digest()) -class LateEnd(Exception): - pass +def hash160(data): + return pack.IntType(160).unpack(hashlib.new('ripemd160', hashlib.sha256(data).digest()).digest()) -def read((data, pos), length): - data2 = data[pos:pos + length] - if len(data2) != length: - raise EarlyEnd() - return data2, (data, pos + length) +def scrypt(data): + return pack.IntType(256).unpack(__import__('ltc_scrypt').getPoWHash(data)) -def size((data, pos)): - return len(data) - pos - -class Type(object): - __slots__ = [] - - # the same data can have only one unpacked representation, but multiple packed binary representations - - def __hash__(self): - rval = getattr(self, '_hash', None) - if rval is None: - try: - rval = self._hash = hash((type(self), frozenset(self.__dict__.items()))) - except: - print self.__dict__ - raise - return rval - - def __eq__(self, other): - return type(other) is type(self) and other.__dict__ == self.__dict__ - - def __ne__(self, other): - return not (self == other) - - def _unpack(self, data): - obj, (data2, pos) = self.read((data, 0)) - - assert data2 is data - - if pos != len(data): - raise LateEnd() - - return obj - - def _pack(self, obj): - f = self.write(None, obj) - - res = [] - while f is not None: - res.append(f[1]) - f = f[0] - res.reverse() - return ''.join(res) - - - def unpack(self, data): - obj = self._unpack(data) - - if p2pool.DEBUG: - data2 = self._pack(obj) - if data2 != data: - if self._unpack(data2) != obj: - raise AssertionError() - - return obj - - def pack(self, obj): - data = self._pack(obj) - - if p2pool.DEBUG: - if self._unpack(data) != obj: - raise AssertionError((self._unpack(data), obj)) - - return data - - - def pack_base58(self, obj): - return base58.encode(self.pack(obj)) - - def unpack_base58(self, base58_data): - return self.unpack(base58.decode(base58_data)) - - - def hash160(self, obj): - return IntType(160).unpack(hashlib.new('ripemd160', hashlib.sha256(self.pack(obj)).digest()).digest()) - - def hash256(self, obj): - return IntType(256).unpack(hashlib.sha256(hashlib.sha256(self.pack(obj)).digest()).digest()) - - def scrypt(self, obj): - import ltc_scrypt - return IntType(256).unpack(ltc_scrypt.getPoWHash(self.pack(obj))) - -class VarIntType(Type): - # redundancy doesn't matter here because bitcoin and p2pool both reencode before hashing - def read(self, file): - data, file = read(file, 1) - first = ord(data) - if first < 0xfd: - return first, file - elif first == 0xfd: - desc, length = '') + {8: 'B', 16: 'H', 32: 'I', 64: 'Q'}[bits]) - else: - return object.__new__(cls, bits, endianness) - - def __init__(self, bits, endianness='little'): - assert bits % 8 == 0 - assert endianness in ['little', 'big'] - self.bytes = bits//8 - self.step = -1 if endianness == 'little' else 1 - self.format_str = '%%0%ix' % (2*self.bytes) - self.max = 2**bits - - def read(self, file, b2a_hex=binascii.b2a_hex): - data, file = read(file, self.bytes) - return int(b2a_hex(data[::self.step]), 16), file - - def write(self, file, item, a2b_hex=binascii.a2b_hex): - if not 0 <= item < self.max: - raise ValueError('invalid int value - %r' % (item,)) - return file, a2b_hex(self.format_str % (item,))[::self.step] - -class IPV6AddressType(Type): - def read(self, file): - data, file = read(file, 16) - if data[:12] != '00000000000000000000ffff'.decode('hex'): - raise ValueError('ipv6 addresses not supported yet') - return '.'.join(str(ord(x)) for x in data[12:]), file - - def write(self, file, item): - bits = map(int, item.split('.')) - if len(bits) != 4: - raise ValueError('invalid address: %r' % (bits,)) - data = '00000000000000000000ffff'.decode('hex') + ''.join(chr(x) for x in bits) - assert len(data) == 16, len(data) - return file, data - -_record_types = {} - -def get_record(fields): - fields = tuple(sorted(fields)) - if 'keys' in fields: - raise ValueError() - if fields not in _record_types: - class _Record(object): - __slots__ = fields - def __repr__(self): - return repr(dict(self)) - def __getitem__(self, key): - return getattr(self, key) - def __setitem__(self, key, value): - setattr(self, key, value) - #def __iter__(self): - # for field in self.__slots__: - # yield field, getattr(self, field) - def keys(self): - return self.__slots__ - def __eq__(self, other): - if isinstance(other, dict): - return dict(self) == other - elif isinstance(other, _Record): - return all(self[k] == other[k] for k in self.keys()) - raise TypeError() - def __ne__(self, other): - return not (self == other) - _record_types[fields] = _Record - return _record_types[fields]() - -class ComposedType(Type): - def __init__(self, fields): - self.fields = tuple(fields) - - def read(self, file): - item = get_record(k for k, v in self.fields) - for key, type_ in self.fields: - item[key], file = type_.read(file) - return item, file - - def write(self, file, item): - for key, type_ in self.fields: - file = type_.write(file, item[key]) - return file - -class ChecksummedType(Type): - def __init__(self, inner): +class ChecksummedType(pack.Type): + def __init__(self, inner, checksum_func=lambda data: hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4]): self.inner = inner + self.checksum_func = checksum_func def read(self, file): obj, file = self.inner.read(file) data = self.inner.pack(obj) - checksum, file = read(file, 4) - if checksum != hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4]: + calculated_checksum = self.checksum_func(data) + checksum, file = pack.read(file, len(calculated_checksum)) + if checksum != calculated_checksum: raise ValueError('invalid checksum') return obj, file def write(self, file, item): data = self.inner.pack(item) - return (file, data), hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4] + return (file, data), self.checksum_func(data) class FloatingInteger(object): __slots__ = ['bits', '_target'] @classmethod def from_target_upper_bound(cls, target): - n = bases.natural_to_string(target) + n = math.natural_to_string(target) if n and ord(n[0]) >= 128: n = '\x00' + n bits2 = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1] - bits = struct.unpack(' 1: - hash_list = [merkle_record_type.hash256(dict(left=left, right=left if right is None else right)) - for left, right in zip(hash_list[::2], hash_list[1::2] + [None])] + hash_list = [hash256(merkle_record_type.pack(dict(left=left, right=right))) + for left, right in zip(hash_list[::2], hash_list[1::2] + [hash_list[::2][-1]])] return hash_list[0] -def calculate_merkle_branch(hashes, index): +def calculate_merkle_link(hashes, index): # XXX optimize this - hash_list = [(h, i == index, []) for i, h in enumerate(hashes)] + hash_list = [(lambda _h=h: _h, i == index, []) for i, h in enumerate(hashes)] while len(hash_list) > 1: hash_list = [ ( - merkle_record_type.hash256(dict(left=left, right=right)), + lambda _left=left, _right=right: hash256(merkle_record_type.pack(dict(left=_left(), right=_right()))), left_f or right_f, (left_l if left_f else right_l) + [dict(side=1, hash=right) if left_f else dict(side=0, hash=left)], ) @@ -463,59 +196,93 @@ def calculate_merkle_branch(hashes, index): zip(hash_list[::2], hash_list[1::2] + [hash_list[::2][-1]]) ] - res = [x['hash'] for x in hash_list[0][2]] + res = [x['hash']() for x in hash_list[0][2]] assert hash_list[0][1] - assert check_merkle_branch(hashes[index], index, res) == hash_list[0][0] + if p2pool.DEBUG: + new_hashes = [random.randrange(2**256) if x is None else x + for x in hashes] + assert check_merkle_link(new_hashes[index], dict(branch=res, index=index)) == merkle_hash(new_hashes) assert index == sum(k*2**i for i, k in enumerate([1-x['side'] for x in hash_list[0][2]])) - return res + return dict(branch=res, index=index) -def check_merkle_branch(tip_hash, index, merkle_branch): - return reduce(lambda c, (i, h): merkle_record_type.hash256( - dict(left=h, right=c) if 2**i & index else +def check_merkle_link(tip_hash, link): + if link['index'] >= 2**len(link['branch']): + raise ValueError('index too large') + return reduce(lambda c, (i, h): hash256(merkle_record_type.pack( + dict(left=h, right=c) if (link['index'] >> i) & 1 else dict(left=c, right=h) - ), enumerate(merkle_branch), tip_hash) + )), enumerate(link['branch']), tip_hash) + +# targets def target_to_average_attempts(target): + assert 0 <= target and isinstance(target, (int, long)), target + if target >= 2**256: warnings.warn('target >= 2**256!') return 2**256//(target + 1) +def average_attempts_to_target(average_attempts): + assert average_attempts > 0 + return min(int(2**256/average_attempts - 1 + 0.5), 2**256-1) + def target_to_difficulty(target): + assert 0 <= target and isinstance(target, (int, long)), target + if target >= 2**256: warnings.warn('target >= 2**256!') return (0xffff0000 * 2**(256-64) + 1)/(target + 1) -# tx - -def tx_get_sigop_count(tx): - return sum(script.get_sigop_count(txin['script']) for txin in tx['tx_ins']) + sum(script.get_sigop_count(txout['script']) for txout in tx['tx_outs']) +def difficulty_to_target(difficulty): + assert difficulty >= 0 + if difficulty == 0: return 2**256-1 + return min(int((0xffff0000 * 2**(256-64) + 1)/difficulty - 1 + 0.5), 2**256-1) # human addresses -human_address_type = ChecksummedType(ComposedType([ - ('version', IntType(8)), - ('pubkey_hash', IntType(160)), -])) +base58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -pubkey_type = PassthruType() +def base58_encode(bindata): + bindata2 = bindata.lstrip(chr(0)) + return base58_alphabet[0]*(len(bindata) - len(bindata2)) + math.natural_to_string(math.string_to_natural(bindata2), base58_alphabet) + +def base58_decode(b58data): + b58data2 = b58data.lstrip(base58_alphabet[0]) + return chr(0)*(len(b58data) - len(b58data2)) + math.natural_to_string(math.string_to_natural(b58data2, base58_alphabet)) + +human_address_type = ChecksummedType(pack.ComposedType([ + ('version', pack.IntType(8)), + ('pubkey_hash', pack.IntType(160)), +])) def pubkey_hash_to_address(pubkey_hash, net): - return human_address_type.pack_base58(dict(version=net.ADDRESS_VERSION, pubkey_hash=pubkey_hash)) + return base58_encode(human_address_type.pack(dict(version=net.ADDRESS_VERSION, pubkey_hash=pubkey_hash))) def pubkey_to_address(pubkey, net): - return pubkey_hash_to_address(pubkey_type.hash160(pubkey), net) + return pubkey_hash_to_address(hash160(pubkey), net) def address_to_pubkey_hash(address, net): - x = human_address_type.unpack_base58(address) + x = human_address_type.unpack(base58_decode(address)) if x['version'] != net.ADDRESS_VERSION: raise ValueError('address not for this net!') return x['pubkey_hash'] +def address_to_script(address, net): + x = human_address_type.unpack(base58_decode(address)) + + print x['pubkey_hash'] + + if x['version'] != net.ADDRESS_VERSION: + raise ValueError('address not for this net!') + return '\x76\xa9' + ('\x14' + pack.IntType(160).pack(x['pubkey_hash'])) + '\x88\xac' + + # transactions def pubkey_to_script2(pubkey): - return ('\x41' + pubkey_type.pack(pubkey)) + '\xac' + assert len(pubkey) <= 75 + return (chr(len(pubkey)) + pubkey) + '\xac' def pubkey_hash_to_script2(pubkey_hash): - return '\x76\xa9' + ('\x14' + IntType(160).pack(pubkey_hash)) + '\x88\xac' + return '\x76\xa9' + ('\x14' + pack.IntType(160).pack(pubkey_hash)) + '\x88\xac' def script2_to_address(script2, net): try: @@ -528,7 +295,7 @@ def script2_to_address(script2, net): return pubkey_to_address(pubkey, net) try: - pubkey_hash = IntType(160).unpack(script2[3:-2]) + pubkey_hash = pack.IntType(160).unpack(script2[3:-2]) script2_test2 = pubkey_hash_to_script2(pubkey_hash) except: pass @@ -547,7 +314,7 @@ def script2_to_human(script2, net): return 'Pubkey. Address: %s' % (pubkey_to_address(pubkey, net),) try: - pubkey_hash = IntType(160).unpack(script2[3:-2]) + pubkey_hash = pack.IntType(160).unpack(script2[3:-2]) script2_test2 = pubkey_hash_to_script2(pubkey_hash) except: pass