From: thomasv Date: Wed, 4 Sep 2013 14:46:27 +0000 (+0200) Subject: move transaction code and fix issue #280 X-Git-Url: https://git.novaco.in/?p=electrum-nvc.git;a=commitdiff_plain;h=afac84e231c424dbf2dc9588196114aec37f3f82 move transaction code and fix issue #280 --- diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 83b0c32..a29e1cc 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -41,7 +41,7 @@ except: sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'") from electrum.wallet import format_satoshis -from electrum.bitcoin import Transaction, is_valid +from electrum import Transaction from electrum import mnemonic from electrum import util, bitcoin, commands, Interface, Wallet from electrum import SimpleConfig, Wallet, WalletStorage diff --git a/lib/__init__.py b/lib/__init__.py index 957ae50..f2a27fc 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -8,7 +8,8 @@ from interface import Interface, pick_random_server, DEFAULT_SERVERS from simple_config import SimpleConfig import bitcoin import account -from bitcoin import Transaction, EC_KEY, is_valid +from transaction import Transaction + from mnemonic import mn_encode as mnemonic_encode from mnemonic import mn_decode as mnemonic_decode from commands import protected_commands, known_commands, offline_commands, Commands diff --git a/lib/bitcoin.py b/lib/bitcoin.py index c4e111b..792aa3b 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -460,300 +460,6 @@ def bip32_private_key(sequence, k, chain): MIN_RELAY_TX_FEE = 10000 -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.input_info = None - self.is_complete = True - - @classmethod - def from_io(klass, inputs, outputs): - raw = klass.serialize(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign - self = klass(raw) - self.is_complete = False - self.inputs = inputs - self.outputs = outputs - extras = [] - for i in self.inputs: - e = { 'txid':i['tx_hash'], 'vout':i['index'], 'scriptPubKey':i.get('raw_output_script') } - extras.append(e) - self.input_info = extras - return self - - def __str__(self): - return self.raw - - @classmethod - def multisig_script(klass, public_keys, num=None): - n = len(public_keys) - if num is None: num = n - # supports only "2 of 2", and "2 of 3" transactions - assert num <= n and n in [2,3] - - if num==2: - s = '52' - elif num == 3: - s = '53' - else: - raise - - for k in public_keys: - s += var_int(len(k)/2) - s += k - if n==2: - s += '52' - elif n==3: - s += '53' - else: - raise - s += 'ae' - - return s - - @classmethod - def serialize( klass, inputs, outputs, for_sig = None ): - - s = int_to_hex(1,4) # version - s += var_int( len(inputs) ) # number of inputs - for i in range(len(inputs)): - txin = inputs[i] - s += txin['tx_hash'].decode('hex')[::-1].encode('hex') # prev hash - s += int_to_hex(txin['index'],4) # prev index - - if for_sig is None: - signatures = txin['signatures'] - pubkeys = txin['pubkeys'] - if not txin.get('redeemScript'): - pubkey = pubkeys[0] - sig = signatures[0] - sig = sig + '01' # hashtype - script = op_push(len(sig)/2) - script += sig - script += op_push(len(pubkey)/2) - script += pubkey - else: - script = '00' # op_0 - for sig in signatures: - sig = sig + '01' - script += op_push(len(sig)/2) - script += sig - - redeem_script = klass.multisig_script(pubkeys,2) - script += op_push(len(redeem_script)/2) - script += redeem_script - - elif for_sig==i: - if txin.get('redeemScript'): - script = txin['redeemScript'] # p2sh uses the inner script - else: - script = txin['raw_output_script'] # scriptsig - else: - script='' - s += var_int( len(script)/2 ) # script length - s += script - s += "ffffffff" # sequence - - s += var_int( len(outputs) ) # number of outputs - for output in outputs: - 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 - - s += var_int( len(script)/2 ) # script length - s += script # script - s += int_to_hex(0,4) # lock time - if for_sig is not None and for_sig != -1: - s += int_to_hex(1, 4) # hash type - return s - - - def for_sig(self,i): - return self.serialize(self.inputs, self.outputs, for_sig = i) - - - def hash(self): - return Hash(self.raw.decode('hex') )[::-1].encode('hex') - - - - def sign(self, keypairs): - import deserialize - is_complete = True - print_error("tx.sign(), keypairs:", keypairs) - - for i, txin in enumerate(self.inputs): - - # if the input is multisig, parse redeem script - redeem_script = txin.get('redeemScript') - num, redeem_pubkeys = deserialize.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 - if len(signatures) == num: - continue - - tx_for_sig = self.serialize( self.inputs, self.outputs, for_sig = i ) - for pubkey in redeem_pubkeys: - # check if we have the corresponding private key - if pubkey in keypairs.keys(): - # add signature - sec = keypairs[pubkey] - compressed = is_compressed(sec) - pkey = regenerate_key(sec) - 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( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) - signatures.append( sig.encode('hex') ) - print_error("adding signature for", pubkey) - - txin["signatures"] = signatures - is_complete = is_complete and len(signatures) == num - - self.is_complete = is_complete - self.raw = self.serialize( self.inputs, self.outputs ) - - - def deserialize(self): - import deserialize - vds = deserialize.BCDataStream() - vds.write(self.raw.decode('hex')) - self.d = deserialize.parse_Transaction(vds) - return self.d - - - 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 - return found - - - def get_value(self, addresses, prevout_values): - # return the balance for that tx - is_relevant = False - is_send = False - is_pruned = False - is_partial = False - v_in = v_out = v_out_mine = 0 - - for item in self.inputs: - addr = item.get('address') - if addr in addresses: - is_send = True - is_relevant = True - key = item['prevout_hash'] + ':%d'%item['prevout_n'] - value = prevout_values.get( key ) - if value is None: - is_pruned = True - else: - v_in += value - else: - is_partial = True - - if not is_send: is_partial = False - - for item in self.outputs: - addr, value = item - v_out += value - if addr in addresses: - v_out_mine += value - is_relevant = True - - if is_pruned: - # some inputs are mine: - fee = None - if is_send: - v = v_out_mine - v_out - else: - # no input is mine - v = v_out_mine - - else: - v = v_out_mine - v_in - - if is_partial: - # some inputs are mine, but not all - fee = None - is_send = v < 0 - else: - # all inputs are mine - fee = v_out - v_in - - return is_relevant, is_send, v, fee - - def as_dict(self): - import json - out = { - "hex":self.raw, - "complete":self.is_complete - } - if not self.is_complete: - extras = [] - for i in self.inputs: - e = { 'txid':i['tx_hash'], 'vout':i['index'], - 'scriptPubKey':i.get('raw_output_script'), - 'KeyID':i.get('KeyID'), - 'redeemScript':i.get('redeemScript'), - 'signatures':i.get('signatures'), - 'pubkeys':i.get('pubkeys'), - } - extras.append(e) - self.input_info = extras - - if self.input_info: - out['input_info'] = json.dumps(self.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: - return True - - for o in self.outputs: - value = o[1] - if value < 1000000: - return True - sum = 0 - for i in self.inputs: - age = verifier.get_confirmations(i["tx_hash"])[0] - sum += i["value"] * age - priority = sum / size - print_error(priority, threshold) - return priority < threshold - - def test_bip32(seed, sequence): diff --git a/lib/deserialize.py b/lib/deserialize.py deleted file mode 100644 index 9af5fe0..0000000 --- a/lib/deserialize.py +++ /dev/null @@ -1,392 +0,0 @@ -# this code comes from ABE. it can probably be simplified -# -# - -from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode, hash_160 -from util import print_error -#import socket -import time -import struct - -# -# 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 """ - -class BCDataStream(object): - def __init__(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 += 1 - elif opcode == opcodes.OP_PUSHDATA2: - (nSize,) = struct.unpack_from(' 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 and decoded[i][0]>0: - 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 - print_error("cannot find address in input script", bytes.encode('hex')) - return [], [], "(None)" - - # 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 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][:-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) - - print_error("cannot find address in input script", bytes.encode('hex')) - return [], [], "(None)" - - - -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 public_key_to_bc_address(decoded[0][1]) - - # 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]) - - # p2sh - match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] - if match_decoded(decoded, match): - return hash_160_to_bc_address(decoded[1][1],5) - - return "(None)" - - diff --git a/lib/transaction.py b/lib/transaction.py new file mode 100644 index 0000000..3136bfa --- /dev/null +++ b/lib/transaction.py @@ -0,0 +1,715 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Note: The deserialization code originally comes from ABE. + + +from bitcoin import * +from util import print_error +import time +import struct + +# +# 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 """ + +class BCDataStream(object): + def __init__(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 += 1 + elif opcode == opcodes.OP_PUSHDATA2: + (nSize,) = struct.unpack_from(' 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 and decoded[i][0]>0: + 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 + print_error("cannot find address in input script", bytes.encode('hex')) + return [], [], "(None)" + + # payto_pubkey + match = [ opcodes.OP_PUSHDATA4 ] + if match_decoded(decoded, match): + return None, None, "(pubkey)" + + # 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 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][:-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) + + print_error("cannot find address in input script", bytes.encode('hex')) + return [], [], "(None)" + + + +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 True, public_key_to_bc_address(decoded[0][1]) + + # 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]) + + # 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 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.input_info = None + self.is_complete = True + + @classmethod + def from_io(klass, inputs, outputs): + raw = klass.serialize(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign + self = klass(raw) + self.is_complete = False + self.inputs = inputs + self.outputs = outputs + extras = [] + for i in self.inputs: + e = { 'txid':i['tx_hash'], 'vout':i['index'], 'scriptPubKey':i.get('raw_output_script') } + extras.append(e) + self.input_info = extras + return self + + def __str__(self): + return self.raw + + @classmethod + def multisig_script(klass, public_keys, num=None): + n = len(public_keys) + if num is None: num = n + # supports only "2 of 2", and "2 of 3" transactions + assert num <= n and n in [2,3] + + if num==2: + s = '52' + elif num == 3: + s = '53' + else: + raise + + for k in public_keys: + s += var_int(len(k)/2) + s += k + if n==2: + s += '52' + elif n==3: + s += '53' + else: + raise + s += 'ae' + + return s + + @classmethod + def serialize( klass, inputs, outputs, for_sig = None ): + + s = int_to_hex(1,4) # version + s += var_int( len(inputs) ) # number of inputs + for i in range(len(inputs)): + txin = inputs[i] + s += txin['tx_hash'].decode('hex')[::-1].encode('hex') # prev hash + s += int_to_hex(txin['index'],4) # prev index + + if for_sig is None: + signatures = txin['signatures'] + pubkeys = txin['pubkeys'] + if not txin.get('redeemScript'): + pubkey = pubkeys[0] + sig = signatures[0] + sig = sig + '01' # hashtype + script = op_push(len(sig)/2) + script += sig + script += op_push(len(pubkey)/2) + script += pubkey + else: + script = '00' # op_0 + for sig in signatures: + sig = sig + '01' + script += op_push(len(sig)/2) + script += sig + + redeem_script = klass.multisig_script(pubkeys,2) + script += op_push(len(redeem_script)/2) + script += redeem_script + + elif for_sig==i: + if txin.get('redeemScript'): + script = txin['redeemScript'] # p2sh uses the inner script + else: + script = txin['raw_output_script'] # scriptsig + else: + script='' + s += var_int( len(script)/2 ) # script length + s += script + s += "ffffffff" # sequence + + s += var_int( len(outputs) ) # number of outputs + for output in outputs: + 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 + + s += var_int( len(script)/2 ) # script length + s += script # script + s += int_to_hex(0,4) # lock time + if for_sig is not None and for_sig != -1: + s += int_to_hex(1, 4) # hash type + return s + + + def for_sig(self,i): + return self.serialize(self.inputs, self.outputs, for_sig = i) + + + def hash(self): + return Hash(self.raw.decode('hex') )[::-1].encode('hex') + + + + def sign(self, keypairs): + is_complete = True + print_error("tx.sign(), keypairs:", keypairs) + + 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 + if len(signatures) == num: + continue + + tx_for_sig = self.serialize( self.inputs, self.outputs, for_sig = i ) + for pubkey in redeem_pubkeys: + # check if we have the corresponding private key + if pubkey in keypairs.keys(): + # add signature + sec = keypairs[pubkey] + compressed = is_compressed(sec) + pkey = regenerate_key(sec) + 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( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) + assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) + signatures.append( sig.encode('hex') ) + print_error("adding signature for", pubkey) + + txin["signatures"] = signatures + is_complete = is_complete and len(signatures) == num + + self.is_complete = 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 + + d['address'] = address + 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['raw_output_script'] = scriptPubKey.encode('hex') + d['index'] = i + return d + + + def add_extra_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')] + print_error("found pay-to-pubkey address:", address) + i["address"] = address + + + 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 + return found + + + def get_value(self, addresses, prevout_values): + # return the balance for that tx + is_relevant = False + is_send = False + is_pruned = False + is_partial = False + v_in = v_out = v_out_mine = 0 + + for item in self.inputs: + addr = item.get('address') + if addr in addresses: + is_send = True + is_relevant = True + key = item['prevout_hash'] + ':%d'%item['prevout_n'] + value = prevout_values.get( key ) + if value is None: + is_pruned = True + else: + v_in += value + else: + is_partial = True + + if not is_send: is_partial = False + + for item in self.outputs: + addr, value = item + v_out += value + if addr in addresses: + v_out_mine += value + is_relevant = True + + if is_pruned: + # some inputs are mine: + fee = None + if is_send: + v = v_out_mine - v_out + else: + # no input is mine + v = v_out_mine + + else: + v = v_out_mine - v_in + + if is_partial: + # some inputs are mine, but not all + fee = None + is_send = v < 0 + else: + # all inputs are mine + fee = v_out - v_in + + return is_relevant, is_send, v, fee + + def as_dict(self): + import json + out = { + "hex":self.raw, + "complete":self.is_complete + } + if not self.is_complete: + extras = [] + for i in self.inputs: + e = { 'txid':i['tx_hash'], 'vout':i['index'], + 'scriptPubKey':i.get('raw_output_script'), + 'KeyID':i.get('KeyID'), + 'redeemScript':i.get('redeemScript'), + 'signatures':i.get('signatures'), + 'pubkeys':i.get('pubkeys'), + } + extras.append(e) + self.input_info = extras + + if self.input_info: + out['input_info'] = json.dumps(self.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: + return True + + for o in self.outputs: + value = o[1] + if value < 1000000: + return True + sum = 0 + for i in self.inputs: + age = verifier.get_confirmations(i["tx_hash"])[0] + sum += i["value"] * age + priority = sum / size + print_error(priority, threshold) + return priority < threshold + + diff --git a/lib/wallet.py b/lib/wallet.py index aa17beb..9800be0 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -33,6 +33,7 @@ import time from util import print_msg, print_error, format_satoshis from bitcoin import * from account import * +from transaction import Transaction # AES encryption EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) @@ -164,12 +165,17 @@ class Wallet: self.load_accounts() self.transactions = {} - tx = storage.get('transactions',{}) - try: - for k,v in tx.items(): self.transactions[k] = Transaction(v) - except: - print_msg("Warning: Cannot deserialize transactions. skipping") - + tx_list = self.storage.get('transactions',{}) + for k,v in tx_list.items(): + tx = Transaction(v) + try: + tx = Transaction(v) + except: + print_msg("Warning: Cannot deserialize transactions. skipping") + continue + + self.add_transaction(tx) + # not saved self.prevout_values = {} # my own transaction outputs self.spent_outputs = [] @@ -194,6 +200,17 @@ class Wallet: self.transactions.pop(tx_hash) + def add_transaction(self, tx): + h = tx.hash() + self.transactions[h] = tx + # find the address corresponding to pay-to-pubkey inputs + tx.add_extra_addresses(self.transactions) + for o in tx.d.get('outputs'): + if o.get('is_pubkey'): + for tx2 in self.transactions.values(): + tx2.add_extra_addresses({h:tx}) + + def set_up_to_date(self,b): with self.lock: self.up_to_date = b @@ -521,7 +538,7 @@ class Wallet: def signrawtransaction(self, tx, input_info, private_keys, password): - import deserialize + unspent_coins = self.get_unspent_coins() seed = self.decode_seed(password) @@ -587,7 +604,7 @@ class Wallet: if redeem_script: addr = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) else: - addr = deserialize.get_address_from_output_script(txin["raw_output_script"].decode('hex')) + addr = transaction.get_address_from_output_script(txin["raw_output_script"].decode('hex')) txin['address'] = addr # add private keys that are in the wallet @@ -1002,10 +1019,8 @@ class Wallet: return with self.transaction_lock: - self.transactions[tx_hash] = tx - + self.add_transaction(tx) self.interface.pending_transactions_for_notifications.append(tx) - self.save_transactions() if self.verifier and tx_height>0: self.verifier.add(tx_hash, tx_height) diff --git a/plugins/qrscanner.py b/plugins/qrscanner.py index c304464..97b588d 100644 --- a/plugins/qrscanner.py +++ b/plugins/qrscanner.py @@ -5,7 +5,8 @@ from PyQt4.QtCore import Qt from electrum_gui.i18n import _ import re -from electrum.bitcoin import MIN_RELAY_TX_FEE, Transaction, is_valid +from electrum import Transaction +from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid from electrum_gui.qrcodewidget import QRCodeWidget import electrum_gui.bmp import json diff --git a/setup.py b/setup.py index ce590f6..02eed26 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,7 @@ setup(name = "Electrum", 'electrum.util', 'electrum.account', 'electrum.bitcoin', - 'electrum.deserialize', + 'electrum.transaction', 'electrum.verifier', 'electrum_gui.gui_gtk', 'electrum_gui.qt_console',