X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=lib%2Ftransaction.py;h=cf7f81bdd5a0a4ae808a48f07fca991ed40e34e5;hb=e327418f6eac8752bb6f95e764cce36b0c339c6e;hp=000e471c334ec7f4f6d18df515a9d53884ebca5d;hpb=3f15a8f217ddfa6fd31a3a128003220b69f310cf;p=electrum-nvc.git diff --git a/lib/transaction.py b/lib/transaction.py index 000e471..cf7f81b 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -33,6 +33,8 @@ import struct import StringIO import mmap +NO_SIGNATURE = 'ff' + class SerializationError(Exception): """ Thrown when there's a problem deserializing or serializing """ @@ -303,17 +305,26 @@ def parse_sig(x_sig): if sig[-2:] == '01': s.append(sig[:-2]) else: - assert sig == 'ff' + 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.get_pubkey_from_x(xpub, s[0], s[1]) + 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) @@ -334,6 +345,12 @@ def parse_scriptSig(d, bytes): # payto_pubkey match = [ opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): + 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 @@ -399,43 +416,104 @@ 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 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 hash_160_to_bc_address(decoded[1][1],5) + + return "(None)" - return False, "(None)" -class Transaction: - def __init__(self, raw): - 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()) + address = get_address_from_output_script(scriptPubKey) + 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() + 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.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, inputs, outputs, locktime=0): self.inputs = inputs self.outputs = outputs + self.locktime = locktime + self.raw = None + + @classmethod + def deserialize(klass, raw): + self = klass([],[]) + self.update(raw) return self + def update(self, raw): + d = deserialize(raw) + self.raw = raw + self.inputs = d['inputs'] + self.outputs = map(lambda x: (x['address'], x['value']), d['outputs']) + self.locktime = d['lockTime'] + + @classmethod def sweep(klass, privkeys, network, to_address, fee): inputs = [] @@ -457,7 +535,7 @@ class Transaction: total = sum( map(lambda x:int(x.get('value')), inputs) ) - fee outputs = [(to_address, total)] - self = klass.from_io(inputs, outputs) + self = klass(inputs, outputs) self.sign({ pubkey:privkey }) return self @@ -491,16 +569,17 @@ class Transaction: @classmethod def pay_script(self, addr): + if addr.startswith('OP_RETURN:'): + h = addr[10:].encode('hex') + return '6a' + push_script(h) 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 += push_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 += push_script(hash_160.encode('hex')) script += '87' # op_equal else: raise @@ -508,11 +587,8 @@ class Transaction: @classmethod - def serialize( klass, inputs, outputs, for_sig = None ): - - NO_SIGNATURE = 'ff' + def serialize(klass, inputs, outputs, for_sig = None ): - push_script = lambda x: op_push(len(x)/2) + x s = int_to_hex(1,4) # version s += var_int( len(inputs) ) # number of inputs for i in range(len(inputs)): @@ -522,38 +598,34 @@ class Transaction: s += int_to_hex(txin['prevout_n'],4) # prev index p2sh = txin.get('redeemScript') is not None - n_sig = 2 if p2sh else 1 - - pubkeys = txin['pubkeys'] # pubkeys should always be known + num_sig = txin['num_sig'] address = txin['address'] - if for_sig is None: + x_signatures = txin['signatures'] + signatures = filter(lambda x: x is not None, x_signatures) + is_complete = len(signatures) == num_sig - # list of signatures - signatures = txin.get('signatures',[]) + if for_sig is None: + # if we have enough signatures, we use the actual pubkeys + # use extended pubkeys (with bip32 derivation) sig_list = [] - for signature in signatures: - sig_list.append(signature + '01') - if len(sig_list) > n_sig: - sig_list = sig_list[:n_sig] - while len(sig_list) < n_sig: - sig_list.append(NO_SIGNATURE) - sig_list = ''.join( map( lambda x: push_script(x), sig_list)) - - if len(signatures) < n_sig: - # extended pubkeys (with bip32 derivation) - x_pubkeys = txin['x_pubkeys'] + if is_complete: + pubkeys = txin['pubkeys'] + for signature in signatures: + sig_list.append(signature + '01') else: - # if we have enough signatures, we use the actual pubkeys - x_pubkeys = txin['pubkeys'] + pubkeys = txin['x_pubkeys'] + for signature in x_signatures: + sig_list.append((signature + '01') if signature is not None else NO_SIGNATURE) + sig_list = ''.join( map( lambda x: push_script(x), sig_list)) if not p2sh: script = sig_list - script += push_script(x_pubkeys[0]) + script += push_script(pubkeys[0]) else: script = '00' # op_0 script += sig_list - redeem_script = klass.multisig_script(x_pubkeys,2) + redeem_script = klass.multisig_script(pubkeys,2) script += push_script(redeem_script) elif for_sig==i: @@ -585,30 +657,59 @@ class Transaction: 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",[]) - if sig not in signatures: - signatures.append(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) - # replace x_pubkey - i = txin['pubkeys'].index(pubkey) - txin['x_pubkeys'][i] = pubkey + self.raw = self.serialize(self.inputs, self.outputs) - self.raw = self.serialize( 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): - pubkeys = txin['pubkeys'] - signatures = txin.get("signatures",{}) + 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): @@ -616,15 +717,13 @@ class Transaction: for i, txin in enumerate(self.inputs): - redeem_pubkeys = txin['pubkeys'] - num = len(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(): @@ -643,81 +742,42 @@ class Transaction: 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 = {} - 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(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: 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')] + address, value = prev_tx.get_outputs()[i.get('prevout_n')] print_error("found pay-to-pubkey address:", address) i["address"] = address + def get_outputs(self): + """convert pubkeys to addresses""" + o = [] + for x, v in self.outputs: + if bitcoin.is_address(x): + addr = x + elif x.startswith('pubkey:'): + addr = public_key_to_bc_address(x[7:].decode('hex')) + else: + addr = "(None)" + o.append((addr,v)) + return o + + def get_output_addresses(self): + return map(lambda x:x[0], self.get_outputs()) + + def has_address(self, addr): found = False for txin in self.inputs: 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 @@ -745,8 +805,7 @@ class Transaction: if not is_send: is_partial = False - for item in self.outputs: - addr, value = item + for addr, value in self.get_outputs(): v_out += value if addr in addresses: v_out_mine += value