1
0
This commit is contained in:
Dmitry Sorokin
2017-01-22 21:25:24 +03:00
committed by ThomasV
parent f70408cef5
commit 5be78950ca
64 changed files with 1232 additions and 657 deletions

View File

@@ -27,11 +27,12 @@
# Note: The deserialization code originally comes from ABE.
from . import bitcoin
from .bitcoin import *
from .util import print_error, profiler, to_string
import bitcoin
from bitcoin import *
from bitcoin import hash160_to_p2sh, hash160_to_p2pkh
from util import print_error, profiler
from . import bitcoin
from .bitcoin import *
import time
import sys
import struct
@@ -40,9 +41,9 @@ import struct
# Workalike python implementation of Bitcoin's CDataStream class.
#
import struct
import StringIO
from six import StringIO
import random
from keystore import xpubkey_to_address, xpubkey_to_pubkey
from .keystore import xpubkey_to_address, xpubkey_to_pubkey
NO_SIGNATURE = 'ff'
@@ -50,6 +51,7 @@ NO_SIGNATURE = 'ff'
class SerializationError(Exception):
""" Thrown when there's a problem deserializing or serializing """
class BCDataStream(object):
def __init__(self):
self.input = None
@@ -59,13 +61,13 @@ class BCDataStream(object):
self.input = None
self.read_cursor = 0
def write(self, bytes): # Initialize with string of bytes
def write(self, _bytes): # Initialize with string of _bytes
if self.input is None:
self.input = bytes
self.input = bytearray(_bytes)
else:
self.input += bytes
self.input += bytearray(_bytes)
def read_string(self):
def read_string(self, encoding='ascii'):
# 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
@@ -81,9 +83,10 @@ class BCDataStream(object):
except IndexError:
raise SerializationError("attempt to read past end of buffer")
return self.read_bytes(length)
return self.read_bytes(length).decode(encoding)
def write_string(self, string):
def write_string(self, string, encoding='ascii'):
string = to_bytes(string, encoding)
# Length-encoded as with read-string
self.write_compact_size(len(string))
self.write(string)
@@ -115,7 +118,7 @@ class BCDataStream(object):
def write_uint64(self, val): return self._write_num('<Q', val)
def read_compact_size(self):
size = ord(self.input[self.read_cursor])
size = self.input[self.read_cursor]
self.read_cursor += 1
if size == 253:
size = self._read_num('<H')
@@ -129,15 +132,15 @@ class BCDataStream(object):
if size < 0:
raise SerializationError("attempt to write size < 0")
elif size < 253:
self.write(chr(size))
self.write(bytes([size]))
elif size < 2**16:
self.write('\xfd')
self.write(b'\xfd')
self._write_num('<H', size)
elif size < 2**32:
self.write('\xfe')
self.write(b'\xfe')
self._write_num('<I', size)
elif size < 2**64:
self.write('\xff')
self.write(b'\xff')
self._write_num('<Q', size)
def _read_num(self, format):
@@ -149,15 +152,13 @@ class BCDataStream(object):
s = struct.pack(format, num)
self.write(s)
#
# enum-like type
# From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/
#
import types, string, exceptions
class EnumException(exceptions.Exception):
class EnumException(Exception):
pass
class Enumeration:
def __init__(self, name, enumList):
self.__doc__ = name
@@ -167,16 +168,16 @@ class Enumeration:
uniqueNames = [ ]
uniqueValues = [ ]
for x in enumList:
if type(x) == types.TupleType:
if isinstance(x, tuple):
x, i = x
if type(x) != types.StringType:
raise EnumException, "enum name is not a string: " + x
if type(i) != types.IntType:
raise EnumException, "enum value is not an integer: " + i
if not isinstance(x, six.text_type):
raise EnumException("enum name is not a string: " + x)
if not isinstance(i, six.integer_types):
raise EnumException("enum value is not an integer: " + i)
if x in uniqueNames:
raise EnumException, "enum name is not unique: " + x
raise EnumException("enum name is not unique: " + x)
if i in uniqueValues:
raise EnumException, "enum value is not unique for " + x
raise EnumException("enum value is not unique for " + x)
uniqueNames.append(x)
uniqueValues.append(i)
lookup[x] = i
@@ -184,8 +185,9 @@ class Enumeration:
i = i + 1
self.lookup = lookup
self.reverseLookup = reverseLookup
def __getattr__(self, attr):
if not self.lookup.has_key(attr):
if attr not in self.lookup:
raise AttributeError
return self.lookup[attr]
def whatis(self, value):
@@ -228,32 +230,32 @@ opcodes = Enumeration("Opcodes", [
])
def script_GetOp(bytes):
def script_GetOp(_bytes):
i = 0
while i < len(bytes):
while i < len(_bytes):
vch = None
opcode = ord(bytes[i])
opcode = _bytes[i]
i += 1
if opcode >= opcodes.OP_SINGLEBYTE_END:
opcode <<= 8
opcode |= ord(bytes[i])
opcode |= _bytes[i]
i += 1
if opcode <= opcodes.OP_PUSHDATA4:
nSize = opcode
if opcode == opcodes.OP_PUSHDATA1:
nSize = ord(bytes[i])
nSize = _bytes[i]
i += 1
elif opcode == opcodes.OP_PUSHDATA2:
(nSize,) = struct.unpack_from('<H', bytes, i)
(nSize,) = struct.unpack_from('<H', _bytes, i)
i += 2
elif opcode == opcodes.OP_PUSHDATA4:
(nSize,) = struct.unpack_from('<I', bytes, i)
(nSize,) = struct.unpack_from('<I', _bytes, i)
i += 4
vch = bytes[i:i+nSize]
vch = _bytes[i:i + nSize]
i += nSize
yield (opcode, vch, i)
yield opcode, vch, i
def script_GetOpName(opcode):
@@ -292,21 +294,20 @@ def safe_parse_pubkey(x):
except:
return x
def parse_scriptSig(d, bytes):
def parse_scriptSig(d, _bytes):
try:
decoded = [ x for x in script_GetOp(bytes) ]
except Exception:
decoded = [ x for x in script_GetOp(_bytes) ]
except Exception as e:
# coinbase transactions raise an exception
print_error("cannot find address in input script", bytes.encode('hex'))
print_error("cannot find address in input script", bh2u(_bytes))
return
match = [ opcodes.OP_PUSHDATA4 ]
if match_decoded(decoded, match):
item = decoded[0][1]
if item[0] == chr(0):
redeemScript = item.encode('hex')
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(redeemScript.decode('hex')))
if item[0] == 0:
redeemScript = bh2u(item)
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
d['type'] = 'p2wpkh-p2sh'
d['redeemScript'] = redeemScript
d['x_pubkeys'] = ["(witness)"]
@@ -317,7 +318,7 @@ def parse_scriptSig(d, bytes):
# payto_pubkey
d['type'] = 'p2pk'
d['address'] = "(pubkey)"
d['signatures'] = [item.encode('hex')]
d['signatures'] = [bh2u(item)]
d['num_sig'] = 1
d['x_pubkeys'] = ["(pubkey)"]
d['pubkeys'] = ["(pubkey)"]
@@ -328,13 +329,13 @@ def parse_scriptSig(d, bytes):
# (65 bytes) onto the stack:
match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ]
if match_decoded(decoded, match):
sig = decoded[0][1].encode('hex')
x_pubkey = decoded[1][1].encode('hex')
sig = bh2u(decoded[0][1])
x_pubkey = bh2u(decoded[1][1])
try:
signatures = parse_sig([sig])
pubkey, address = xpubkey_to_address(x_pubkey)
except BaseException:
print_error("cannot find address in input script", bytes.encode('hex'))
except:
print_error("cannot find address in input script", bh2u(_bytes))
return
d['type'] = 'p2pkh'
d['signatures'] = signatures
@@ -347,9 +348,9 @@ def parse_scriptSig(d, bytes):
# p2sh transaction, m of n
match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)
if not match_decoded(decoded, match):
print_error("cannot find address in input script", bytes.encode('hex'))
print_error("cannot find address in input script", bh2u(_bytes))
return
x_sig = [x[1].encode('hex') for x in decoded[1:-1]]
x_sig = [bh2u(x[1]) for x in decoded[1:-1]]
dec2 = [ x for x in script_GetOp(decoded[-1][1]) ]
m = dec2[0][0] - opcodes.OP_1 + 1
n = dec2[-2][0] - opcodes.OP_1 + 1
@@ -357,9 +358,9 @@ def parse_scriptSig(d, bytes):
op_n = opcodes.OP_1 + n - 1
match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
if not match_decoded(dec2, match_multisig):
print_error("cannot find address in input script", bytes.encode('hex'))
print_error("cannot find address in input script", bh2u(_bytes))
return
x_pubkeys = map(lambda x: x[1].encode('hex'), dec2[1:-2])
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
redeemScript = multisig_script(pubkeys, m)
# write result in d
@@ -369,19 +370,17 @@ def parse_scriptSig(d, bytes):
d['x_pubkeys'] = x_pubkeys
d['pubkeys'] = pubkeys
d['redeemScript'] = redeemScript
d['address'] = hash160_to_p2sh(hash_160(redeemScript.decode('hex')))
d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript)))
def get_address_from_output_script(bytes):
decoded = [ x for x in script_GetOp(bytes) ]
def get_address_from_output_script(_bytes):
decoded = [x for x in script_GetOp(_bytes)]
# 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 TYPE_PUBKEY, decoded[0][1].encode('hex')
return TYPE_PUBKEY, bh2u(decoded[0][1])
# Pay-by-Bitcoin-address TxOuts look like:
# DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
@@ -394,10 +393,7 @@ def get_address_from_output_script(bytes):
if match_decoded(decoded, match):
return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1])
return TYPE_SCRIPT, bytes
return TYPE_SCRIPT, _bytes
def parse_input(vds):
@@ -406,7 +402,7 @@ def parse_input(vds):
prevout_n = vds.read_uint32()
scriptSig = vds.read_bytes(vds.read_compact_size())
sequence = vds.read_uint32()
d['scriptSig'] = scriptSig.encode('hex')
d['scriptSig'] = bh2u(scriptSig)
d['prevout_hash'] = prevout_hash
d['prevout_n'] = prevout_n
d['sequence'] = sequence
@@ -433,14 +429,14 @@ def parse_output(vds, i):
d['value'] = vds.read_int64()
scriptPubKey = vds.read_bytes(vds.read_compact_size())
d['type'], d['address'] = get_address_from_output_script(scriptPubKey)
d['scriptPubKey'] = scriptPubKey.encode('hex')
d['scriptPubKey'] = bh2u(scriptPubKey)
d['prevout_n'] = i
return d
def deserialize(raw):
vds = BCDataStream()
vds.write(raw.decode('hex'))
vds.write(bfh(raw))
d = {}
start = vds.read_cursor
d['version'] = vds.read_int32()
@@ -448,13 +444,13 @@ def deserialize(raw):
is_segwit = (n_vin == 0)
if is_segwit:
marker = vds.read_bytes(1)
assert marker == chr(1)
assert marker == 1
n_vin = vds.read_compact_size()
d['inputs'] = list(parse_input(vds) for i in xrange(n_vin))
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
n_vout = vds.read_compact_size()
d['outputs'] = list(parse_output(vds,i) for i in xrange(n_vout))
d['outputs'] = [parse_output(vds,i) for i in range(n_vout)]
if is_segwit:
d['witness'] = list(parse_witness(vds) for i in xrange(n_vin))
d['witness'] = [parse_witness(vds) for i in range(n_vin)]
d['lockTime'] = vds.read_uint32()
return d
@@ -462,27 +458,30 @@ def deserialize(raw):
# pay & redeem scripts
def push_script(x):
return op_push(len(x)/2) + x
return op_push(len(x)//2) + x
def get_scriptPubKey(addr):
addrtype, hash_160 = bc_address_to_hash_160(addr)
if addrtype == bitcoin.ADDRTYPE_P2PKH:
script = '76a9' # op_dup, op_hash_160
script += push_script(hash_160.encode('hex'))
script += push_script(bh2u(hash_160))
script += '88ac' # op_equalverify, op_checksig
elif addrtype == bitcoin.ADDRTYPE_P2SH:
script = 'a9' # op_hash_160
script += push_script(hash_160.encode('hex'))
script += push_script(bh2u(hash_160))
script += '87' # op_equal
else:
raise BaseException('unknown address type')
return script
def segwit_script(pubkey):
pubkey = safe_parse_pubkey(pubkey)
pkh = hash_160(pubkey.decode('hex')).encode('hex')
pkh = bh2u(hash_160(bfh(pubkey)))
return '00' + push_script(pkh)
def multisig_script(public_keys, m):
n = len(public_keys)
assert n <= 15
@@ -505,9 +504,9 @@ class Transaction:
def __init__(self, raw):
if raw is None:
self.raw = None
elif type(raw) in [str, unicode]:
elif isinstance(raw, str):
self.raw = raw.strip() if raw else None
elif type(raw) is dict:
elif isinstance(raw, dict):
self.raw = raw['hex']
else:
raise BaseException("cannot initialize transaction", raw)
@@ -553,15 +552,15 @@ class Transaction:
for sig in sigs2:
if sig in sigs1:
continue
pre_hash = Hash(self.serialize_preimage(i).decode('hex'))
pre_hash = Hash(bfh(self.serialize_preimage(i)))
# der to string
order = ecdsa.ecdsa.generator_secp256k1.order()
r, s = ecdsa.util.sigdecode_der(sig.decode('hex')[:-1], order)
r, s = ecdsa.util.sigdecode_der(bfh(sig[:-2]), order)
sig_string = ecdsa.util.sigencode_string(r, s, order)
compressed = True
for recid in range(4):
public_key = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve = SECP256k1)
pubkey = point_to_ser(public_key.pubkey.point, compressed).encode('hex')
pubkey = bh2u(point_to_ser(public_key.pubkey.point, compressed))
if pubkey in pubkeys:
public_key.verify_digest(sig_string, pre_hash, sigdecode = ecdsa.util.sigdecode_string)
j = pubkeys.index(pubkey)
@@ -572,7 +571,6 @@ class Transaction:
# redo raw
self.raw = self.serialize()
def deserialize(self):
if self.raw is None:
return
@@ -597,7 +595,7 @@ class Transaction:
@classmethod
def pay_script(self, output_type, addr):
if output_type == TYPE_SCRIPT:
return addr.encode('hex')
return bh2u(addr)
elif output_type == TYPE_ADDRESS:
return get_scriptPubKey(addr)
else:
@@ -616,7 +614,7 @@ class Transaction:
else:
pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
x_signatures = txin['signatures']
signatures = filter(None, x_signatures)
signatures = list(filter(None, x_signatures))
is_complete = len(signatures) == num_sig
if is_complete:
pk_list = pubkeys
@@ -671,21 +669,21 @@ class Transaction:
return multisig_script(pubkeys, txin['num_sig'])
elif txin['type'] == 'p2wpkh-p2sh':
pubkey = txin['pubkeys'][0]
pkh = bitcoin.hash_160(pubkey.decode('hex')).encode('hex')
pkh = bh2u(bitcoin.hash_160(bfh(pubkey)))
return '76a9' + push_script(pkh) + '88ac'
else:
raise TypeError('Unknown txin type', _type)
@classmethod
def serialize_outpoint(self, txin):
return txin['prevout_hash'].decode('hex')[::-1].encode('hex') + int_to_hex(txin['prevout_n'], 4)
return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex(txin['prevout_n'], 4)
@classmethod
def serialize_input(self, txin, script):
# Prev hash and index
s = self.serialize_outpoint(txin)
# Script length, script, sequence
s += var_int(len(script)/2)
s += var_int(len(script)//2)
s += script
s += int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
return s
@@ -704,7 +702,7 @@ class Transaction:
output_type, addr, amount = output
s = int_to_hex(amount, 8)
script = self.pay_script(output_type, addr)
s += var_int(len(script)/2)
s += var_int(len(script)//2)
s += script
return s
@@ -715,6 +713,7 @@ class Transaction:
inputs = self.inputs()
outputs = self.outputs()
txin = inputs[i]
# TODO: py3 hex
if self.is_segwit_input(txin):
hashPrevouts = Hash(''.join(self.serialize_outpoint(txin) for txin in inputs).decode('hex')).encode('hex')
hashSequence = Hash(''.join(int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) for txin in inputs).decode('hex')).encode('hex')
@@ -750,7 +749,7 @@ class Transaction:
return nVersion + txins + txouts + nLocktime
def hash(self):
print "warning: deprecated tx.hash()"
print("warning: deprecated tx.hash()")
return self.txid()
def txid(self):
@@ -758,11 +757,11 @@ class Transaction:
if not all_segwit and not self.is_complete():
return None
ser = self.serialize(witness=False)
return Hash(ser.decode('hex'))[::-1].encode('hex')
return bh2u(Hash(bfh(ser))[::-1])
def wtxid(self):
ser = self.serialize(witness=True)
return Hash(ser.decode('hex'))[::-1].encode('hex')
return bh2u(Hash(bfh(ser))[::-1])
def add_inputs(self, inputs):
self._inputs.extend(inputs)
@@ -787,13 +786,13 @@ class Transaction:
@profiler
def estimated_size(self):
'''Return an estimated tx size in bytes.'''
return len(self.serialize(True)) / 2 if not self.is_complete() or self.raw is None else len(self.raw) / 2 # ASCII hex string
return len(self.serialize(True)) // 2 if not self.is_complete() or self.raw is None else len(self.raw) / 2 # ASCII hex string
@classmethod
def estimated_input_size(self, txin):
'''Return an estimated of serialized input size in bytes.'''
script = self.input_script(txin, True)
return len(self.serialize_input(txin, script)) / 2
return len(self.serialize_input(txin, script)) // 2
def signature_count(self):
r = 0
@@ -801,7 +800,7 @@ class Transaction:
for txin in self.inputs():
if txin['type'] == 'coinbase':
continue
signatures = filter(None, txin.get('signatures',[]))
signatures = list(filter(None, txin.get('signatures',[])))
s += len(signatures)
r += txin.get('num_sig',-1)
return s, r
@@ -824,14 +823,14 @@ class Transaction:
sec = keypairs.get(x_pubkey)
pubkey = public_key_from_private_key(sec)
# add signature
pre_hash = Hash(self.serialize_preimage(i).decode('hex'))
pre_hash = Hash(bfh(self.serialize_preimage(i)))
pkey = regenerate_key(sec)
secexp = pkey.secret
private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve = SECP256k1)
public_key = private_key.get_verifying_key()
sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der)
assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der)
txin['signatures'][j] = sig.encode('hex') + '01'
txin['signatures'][j] = bh2u(sig) + '01'
txin['x_pubkeys'][j] = pubkey
txin['pubkeys'][j] = pubkey # needed for fd keys
self._inputs[i] = txin
@@ -845,9 +844,9 @@ class Transaction:
if type == TYPE_ADDRESS:
addr = x
elif type == TYPE_PUBKEY:
addr = bitcoin.public_key_to_p2pkh(x.decode('hex'))
addr = bitcoin.public_key_to_p2pkh(bfh(x))
else:
addr = 'SCRIPT ' + x.encode('hex')
addr = 'SCRIPT ' + bh2u(x)
o.append((addr,v)) # consider using yield (addr, v)
return o
@@ -869,7 +868,6 @@ class Transaction:
}
return out
def requires_fee(self, wallet):
# see https://en.bitcoin.it/wiki/Transaction_fees
#
@@ -899,7 +897,7 @@ def tx_from_str(txt):
import json
txt = txt.strip()
try:
txt.decode('hex')
bfh(txt)
is_hex = True
except:
is_hex = False