X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=lib%2Ftransaction.py;h=595ac7fee52cc90e07a4fc0eb66fe6920da27c44;hb=3cbe11a42473af52e7c5e002c36aaaf32646f627;hp=150c5d8204309325eb4129cafe63012e368622ee;hpb=df59c8e0130067e2bce6aa1120bfcbc859a97eda;p=electrum-nvc.git diff --git a/lib/transaction.py b/lib/transaction.py index 150c5d8..595ac7f 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -20,6 +20,7 @@ # Note: The deserialization code originally comes from ABE. +import bitcoin from bitcoin import * from util import print_error import time @@ -32,6 +33,8 @@ import struct import StringIO import mmap +NO_SIGNATURE = 'ff' + class SerializationError(Exception): """ Thrown when there's a problem deserializing or serializing """ @@ -295,18 +298,60 @@ def match_decoded(decoded, to_match): return False return True -def get_address_from_input_script(bytes): + +def parse_sig(x_sig): + s = [] + for sig in x_sig: + if sig[-2:] == '01': + s.append(sig[:-2]) + else: + assert sig == NO_SIGNATURE + s.append(None) + return s + +def is_extended_pubkey(x_pubkey): + return x_pubkey[0:2] in ['fe', 'ff'] + +def x_to_xpub(x_pubkey): + if x_pubkey[0:2] == 'ff': + from account import BIP32_Account + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + return xpub + + + +def parse_xpub(x_pubkey): + if x_pubkey[0:2] == 'ff': + from account import BIP32_Account + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + pubkey = BIP32_Account.derive_pubkey_from_xpub(xpub, s[0], s[1]) + elif x_pubkey[0:2] == 'fe': + from account import OldAccount + mpk, s = OldAccount.parse_xpubkey(x_pubkey) + pubkey = OldAccount.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1]) + else: + pubkey = x_pubkey + return pubkey + + +def parse_scriptSig(d, bytes): try: decoded = [ x for x in script_GetOp(bytes) ] except Exception: # coinbase transactions raise an exception print_error("cannot find address in input script", bytes.encode('hex')) - return [], {}, "(None)" + return # payto_pubkey match = [ opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): - return None, {}, "(pubkey)" + sig = decoded[0][1].encode('hex') + d['address'] = "(pubkey)" + d['signatures'] = [sig] + d['num_sig'] = 1 + d['x_pubkeys'] = ["(pubkey)"] + d['pubkeys'] = ["(pubkey)"] + return # non-generated TxIn transactions push a signature # (seventy-something bytes) and then their public key @@ -314,38 +359,53 @@ def get_address_from_input_script(bytes): match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): sig = decoded[0][1].encode('hex') - assert sig[-2:] == '01' - sig = sig[:-2] - pubkey = decoded[1][1].encode('hex') - return [pubkey], {pubkey:sig}, public_key_to_bc_address(pubkey.decode('hex')) + x_pubkey = decoded[1][1].encode('hex') + try: + signatures = parse_sig([sig]) + pubkey = parse_xpub(x_pubkey) + except: + import traceback + traceback.print_exc(file=sys.stdout) + print_error("cannot find address in input script", bytes.encode('hex')) + return + d['signatures'] = signatures + d['x_pubkeys'] = [x_pubkey] + d['num_sig'] = 1 + d['pubkeys'] = [pubkey] + d['address'] = public_key_to_bc_address(pubkey.decode('hex')) + return # 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][:-1].encode('hex'), decoded[1:-1]) - - dec2 = [ x for x in script_GetOp(redeemScript) ] + if not match_decoded(decoded, match): + print_error("cannot find address in input script", bytes.encode('hex')) + return + + x_sig = map(lambda x:x[1].encode('hex'), decoded[1:-1]) + d['signatures'] = parse_sig(x_sig) + d['num_sig'] = 2 + + dec2 = [ x for x in script_GetOp(decoded[-1][1]) ] + match_2of2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] + match_2of3 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec2, match_2of2): + x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ] + elif match_decoded(dec2, match_2of3): + x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] + else: + print_error("cannot find address in input script", bytes.encode('hex')) + return - # 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) + d['x_pubkeys'] = x_pubkeys + pubkeys = map(parse_xpub, x_pubkeys) + d['pubkeys'] = pubkeys + redeemScript = Transaction.multisig_script(pubkeys,2) + d['redeemScript'] = redeemScript + d['address'] = hash_160_to_bc_address(hash_160(redeemScript.decode('hex')), 20) - print_error("cannot find address in input script", bytes.encode('hex')) - return [], {}, "(None)" @@ -356,42 +416,130 @@ def get_address_from_output_script(bytes): # 65 BYTES:... CHECKSIG match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): - return True, public_key_to_bc_address(decoded[0][1]) + return 'pubkey', decoded[0][1].encode('hex') # 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 False, hash_160_to_bc_address(decoded[2][1]) + return 'address', hash_160_to_bc_address(decoded[2][1]) # p2sh match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] if match_decoded(decoded, match): - return False, hash_160_to_bc_address(decoded[1][1],5) + return 'address', hash_160_to_bc_address(decoded[1][1],20) + + return "(None)", "(None)" - return False, "(None)" -class Transaction: - def __init__(self, raw, is_complete = True): - self.raw = raw - self.deserialize() - self.inputs = self.d['inputs'] - self.outputs = self.d['outputs'] - self.outputs = map(lambda x: (x['address'],x['value']), self.outputs) - self.locktime = self.d['lockTime'] - +def parse_input(vds): + d = {} + prevout_hash = hash_encode(vds.read_bytes(32)) + prevout_n = vds.read_uint32() + scriptSig = vds.read_bytes(vds.read_compact_size()) + sequence = vds.read_uint32() + if prevout_hash == '00'*32: + d['is_coinbase'] = True + else: + d['is_coinbase'] = False + d['prevout_hash'] = prevout_hash + d['prevout_n'] = prevout_n + d['sequence'] = sequence + d['pubkeys'] = [] + d['signatures'] = {} + d['address'] = None + if scriptSig: + parse_scriptSig(d, scriptSig) + return d + + +def parse_output(vds, i): + d = {} + d['value'] = vds.read_int64() + scriptPubKey = vds.read_bytes(vds.read_compact_size()) + type, address = get_address_from_output_script(scriptPubKey) + d['type'] = type + d['address'] = address + d['scriptPubKey'] = scriptPubKey.encode('hex') + d['prevout_n'] = i + return d + + +def deserialize(raw): + vds = BCDataStream() + vds.write(raw.decode('hex')) + d = {} + start = vds.read_cursor + d['version'] = vds.read_int32() + d['timestamp'] = vds.read_int32() + n_vin = vds.read_compact_size() + d['inputs'] = [] + for i in xrange(n_vin): + d['inputs'].append(parse_input(vds)) + n_vout = vds.read_compact_size() + d['outputs'] = [] + for i in xrange(n_vout): + d['outputs'].append(parse_output(vds, i)) + d['lockTime'] = vds.read_uint32() + return d + + +push_script = lambda x: op_push(len(x)/2) + x + +class Transaction: + def __str__(self): + if self.raw is None: + self.raw = self.serialize(self.timestamp, self.inputs, self.outputs, for_sig = None) # for_sig=-1 means do not sign return self.raw - @classmethod - def from_io(klass, inputs, outputs): - raw = klass.serialize(inputs, outputs, for_sig = None) # for_sig=-1 means do not sign - self = klass(raw) + def __init__(self, timestamp, inputs, outputs, locktime=0): + self.timestamp = timestamp self.inputs = inputs self.outputs = outputs + self.locktime = locktime + self.raw = None + + @classmethod + def deserialize(klass, raw): + self = klass(0, [],[]) + self.update(raw) + return self + + def update(self, raw): + d = deserialize(raw) + self.raw = raw + self.timestamp = d['timestamp'] + self.inputs = d['inputs'] + self.outputs = map(lambda x: (x['type'], x['address'], x['value']), d['outputs']) + self.locktime = d['lockTime'] + + @classmethod + def sweep(klass, privkeys, network, to_address, fee): + inputs = [] + for privkey in privkeys: + pubkey = public_key_from_private_key(privkey) + address = address_from_private_key(privkey) + u = network.synchronous_get([ ('blockchain.address.listunspent',[address])])[0] + pay_script = klass.pay_script('address', address) + for item in u: + item['scriptPubKey'] = pay_script + item['redeemPubkey'] = pubkey + item['address'] = address + item['prevout_hash'] = item['tx_hash'] + item['prevout_n'] = item['tx_pos'] + inputs += u + + if not inputs: + return + + total = sum( map(lambda x:int(x.get('value')), inputs) ) - fee + outputs = [('address', to_address, total)] + self = klass(inputs, outputs) + self.sign({ pubkey:privkey }) return self @classmethod @@ -409,7 +557,7 @@ class Transaction: raise for k in public_keys: - s += var_int(len(k)/2) + s += op_push(len(k)/2) s += k if n==2: s += '52' @@ -421,46 +569,79 @@ class Transaction: return s + @classmethod - def serialize( klass, inputs, outputs, for_sig = None ): + def pay_script(self, type, payto): + assert type == 'address' or type == 'pubkey' or type == 'op_return' + + if type == 'op_return': + h = payto.encode('hex') + return '6a' + push_script(h) + elif type == 'pubkey': + script = push_script(payto) + script += 'ac' # op_checksig + return script + else: + addrtype, hash_160 = bc_address_to_hash_160(payto) + if addrtype == 8: + script = '76a9' # op_dup, op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '88ac' # op_equalverify, op_checksig + elif addrtype == 20: + script = 'a9' # op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '87' # op_equal + else: + raise + return script - push_script = lambda x: op_push(len(x)/2) + x + + @classmethod + def serialize(klass, timestamp, inputs, outputs, for_sig = None ): s = int_to_hex(1,4) # version + s += int_to_hex(timestamp, 4) # timestamp s += var_int( len(inputs) ) # number of inputs for i in range(len(inputs)): txin = inputs[i] + s += txin['prevout_hash'].decode('hex')[::-1].encode('hex') # prev hash s += int_to_hex(txin['prevout_n'],4) # prev index - signatures = txin.get('signatures', {}) - if for_sig is None and not signatures: - script = '' + p2sh = txin.get('redeemScript') is not None + num_sig = txin['num_sig'] + payto = txin['address'] if txin["type"] == 'address' else txin['pubkeys'][0] + + x_signatures = txin['signatures'] + signatures = filter(lambda x: x is not None, x_signatures) + is_complete = len(signatures) == num_sig + + if for_sig is None: + # if we have enough signatures, we use the actual pubkeys + # use extended pubkeys (with bip32 derivation) + sig_list = [] + if is_complete: + pubkeys = txin['pubkeys'] + for signature in signatures: + sig_list.append(signature + '01') + else: + pubkeys = txin['x_pubkeys'] + for signature in x_signatures: + sig_list.append((signature + '01') if signature is not None else NO_SIGNATURE) - elif for_sig is None: - pubkeys = txin['pubkeys'] - sig_list = '' - for pubkey in pubkeys: - sig = signatures.get(pubkey) - if not sig: - continue - sig = sig + '01' - sig_list += push_script(sig) - - if not txin.get('redeemScript'): + sig_list = ''.join( map( lambda x: push_script(x), sig_list)) + if not p2sh: script = sig_list - script += push_script(pubkeys[0]) + if txin["type"] != 'pubkey': + script += push_script(pubkeys[0]) else: script = '00' # op_0 script += sig_list redeem_script = klass.multisig_script(pubkeys,2) - assert redeem_script == txin.get('redeemScript') script += push_script(redeem_script) elif for_sig==i: - if txin.get('redeemScript'): - script = txin['redeemScript'] # p2sh uses the inner script - else: - script = txin['scriptPubKey'] # scriptsig + script = txin['redeemScript'] if p2sh else klass.pay_script(txin["type"], payto) + print script else: script = '' s += var_int( len(script)/2 ) # script length @@ -469,22 +650,9 @@ class Transaction: s += var_int( len(outputs) ) # number of outputs for output in outputs: - addr, amount = output + type, addr, amount = output s += int_to_hex( amount, 8) # amount - addrtype, hash_160 = bc_address_to_hash_160(addr) - if addrtype == 0: - script = '76a9' # op_dup, op_hash_160 - script += '14' # push 0x14 bytes - script += hash_160.encode('hex') - script += '88ac' # op_equalverify, op_checksig - elif addrtype == 5: - script = 'a9' # op_hash_160 - script += '14' # push 0x14 bytes - script += hash_160.encode('hex') - script += '87' # op_equal - else: - raise - + script = klass.pay_script(type, addr) s += var_int( len(script)/2 ) # script length s += script # script s += int_to_hex(0,4) # lock time @@ -494,33 +662,66 @@ class Transaction: def tx_for_sig(self,i): - return self.serialize(self.inputs, self.outputs, for_sig = i) + return self.serialize(self.timestamp, self.inputs, self.outputs, for_sig = i) def hash(self): return Hash(self.raw.decode('hex') )[::-1].encode('hex') def add_signature(self, i, pubkey, sig): + print_error("adding signature for", pubkey) txin = self.inputs[i] - signatures = txin.get("signatures",{}) - signatures[pubkey] = sig - txin["signatures"] = signatures + pubkeys = txin['pubkeys'] + ii = pubkeys.index(pubkey) + txin['signatures'][ii] = sig + txin['x_pubkeys'][ii] = pubkey self.inputs[i] = txin - print_error("adding signature for", pubkey) - self.raw = self.serialize( self.inputs, self.outputs ) + self.raw = self.serialize(self.timestamp, self.inputs, self.outputs) + + + def signature_count(self): + r = 0 + s = 0 + for txin in self.inputs: + signatures = filter(lambda x: x is not None, txin['signatures']) + s += len(signatures) + r += txin['num_sig'] + return s, r def is_complete(self): - for i, txin in enumerate(self.inputs): - redeem_script = txin.get('redeemScript') - num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) - signatures = txin.get("signatures",{}) - if len(signatures) == num: + s, r = self.signature_count() + return r == s + + + def inputs_to_sign(self): + from account import BIP32_Account, OldAccount + xpub_list = [] + addr_list = set() + for txin in self.inputs: + x_signatures = txin['signatures'] + signatures = filter(lambda x: x is not None, x_signatures) + + if len(signatures) == txin['num_sig']: + # input is complete continue - else: - return False - return True + for k, x_pubkey in enumerate(txin['x_pubkeys']): + + if x_signatures[k] is not None: + # this pubkey already signed + continue + + if x_pubkey[0:2] == 'ff': + xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) + xpub_list.append((xpub,sequence)) + elif x_pubkey[0:2] == 'fe': + xpub, sequence = OldAccount.parse_xpubkey(x_pubkey) + xpub_list.append((xpub,sequence)) + else: + addr_list.add(txin['address']) + + return addr_list, xpub_list def sign(self, keypairs): @@ -528,18 +729,13 @@ class Transaction: for i, txin in enumerate(self.inputs): - # if the input is multisig, parse redeem script - redeem_script = txin.get('redeemScript') - num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) - - # add pubkeys - txin["pubkeys"] = redeem_pubkeys - # get list of already existing signatures - signatures = txin.get("signatures",{}) # continue if this txin is complete + signatures = filter(lambda x: x is not None, txin['signatures']) + num = txin['num_sig'] if len(signatures) == num: continue + redeem_pubkeys = txin['pubkeys'] for_sig = Hash(self.tx_for_sig(i).decode('hex')) for pubkey in redeem_pubkeys: if pubkey in keypairs.keys(): @@ -549,75 +745,43 @@ class Transaction: secexp = pkey.secret private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) public_key = private_key.get_verifying_key() - sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der ) + sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der_canonize ) + assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der) self.add_signature(i, pubkey, sig.encode('hex')) - print_error("is_complete", self.is_complete()) - self.raw = self.serialize( self.inputs, self.outputs ) - - - - def deserialize(self): - vds = BCDataStream() - vds.write(self.raw.decode('hex')) - d = {} - start = vds.read_cursor - d['version'] = vds.read_int32() - n_vin = vds.read_compact_size() - d['inputs'] = [] - for i in xrange(n_vin): - d['inputs'].append(self.parse_input(vds)) - n_vout = vds.read_compact_size() - d['outputs'] = [] - for i in xrange(n_vout): - d['outputs'].append(self.parse_output(vds, i)) - d['lockTime'] = vds.read_uint32() - self.d = d - return self.d - - - def parse_input(self, vds): - d = {} - d['prevout_hash'] = hash_encode(vds.read_bytes(32)) - d['prevout_n'] = vds.read_uint32() - scriptSig = vds.read_bytes(vds.read_compact_size()) - d['sequence'] = vds.read_uint32() - - if scriptSig: - pubkeys, signatures, address = get_address_from_input_script(scriptSig) - else: - pubkeys = [] - signatures = {} - address = None + self.raw = self.serialize( self.timestamp, self.inputs, self.outputs ) - d['address'] = address - d['pubkeys'] = pubkeys - d['signatures'] = signatures - return d - - def parse_output(self, vds, i): - d = {} - d['value'] = vds.read_int64() - scriptPubKey = vds.read_bytes(vds.read_compact_size()) - is_pubkey, address = get_address_from_output_script(scriptPubKey) - d['is_pubkey'] = is_pubkey - d['address'] = address - d['scriptPubKey'] = scriptPubKey.encode('hex') - d['prevout_n'] = i - return d - - - def add_extra_addresses(self, txlist): + def add_pubkey_addresses(self, txlist): for i in self.inputs: + i["pubkey"] = False if i.get("address") == "(pubkey)": prev_tx = txlist.get(i.get('prevout_hash')) if prev_tx: - address, value = prev_tx.outputs[i.get('prevout_n')] + type, address, value = prev_tx.get_outputs()[i.get('prevout_n')] print_error("found pay-to-pubkey address:", address) i["address"] = address + i["type"] = type + + + def get_outputs(self): + """convert pubkeys to addresses""" + o = [] + for type, x, v in self.outputs: + if type == 'address': + addr = x + elif type == 'pubkey': + addr = public_key_to_bc_address(x.decode('hex')) + else: + addr = "(None)" + o.append((type,addr,v)) +# print o + return o + + def get_output_addresses(self): + return map(lambda x:x[1], self.get_outputs()) def has_address(self, addr): @@ -626,10 +790,9 @@ class Transaction: if addr == txin.get('address'): found = True break - for txout in self.outputs: - if addr == txout[0]: - found = True - break + if addr in self.get_output_addresses(): + found = True + return found @@ -657,8 +820,7 @@ class Transaction: if not is_send: is_partial = False - for item in self.outputs: - addr, value = item + for type, addr, value in self.get_outputs(): v_out += value if addr in addresses: v_out_mine += value @@ -687,48 +849,24 @@ class Transaction: return is_relevant, is_send, v, fee - def get_input_info(self): - info = [] - for i in self.inputs: - item = { - 'prevout_hash':i['prevout_hash'], - 'prevout_n':i['prevout_n'], - 'address':i.get('address'), - 'KeyID':i.get('KeyID'), - 'scriptPubKey':i.get('scriptPubKey'), - 'redeemScript':i.get('redeemScript'), - 'redeemPubkey':i.get('redeemPubkey'), - 'pubkeys':i.get('pubkeys'), - 'signatures':i.get('signatures',{}), - } - info.append(item) - return info - - def as_dict(self): import json out = { "hex":self.raw, "complete":self.is_complete() } - - if not self.is_complete(): - input_info = self.get_input_info() - out['input_info'] = json.dumps(input_info).replace(' ','') - return out def requires_fee(self, verifier): - # see https://en.bitcoin.it/wiki/Transaction_fees threshold = 57600000 size = len(self.raw)/2 - if size >= 10000: + if size >= 1000: return True - for o in self.outputs: - value = o[1] - if value < 1000000: + for o in self.get_outputs(): + value = o[2] + if value < 10000: return True sum = 0 for i in self.inputs: @@ -740,12 +878,3 @@ class Transaction: - def add_input_info(self, input_info): - for i, txin in enumerate(self.inputs): - item = input_info[i] - txin['address'] = item['address'] - txin['signatures'] = item['signatures'] - txin['scriptPubKey'] = item['scriptPubKey'] - txin['redeemScript'] = item.get('redeemScript') - txin['redeemPubkey'] = item.get('redeemPubkey') - txin['KeyID'] = item.get('KeyID')