electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit afac84e231c424dbf2dc9588196114aec37f3f82
parent d2099d94e5b3ac3be5a8e09566189bb04317a8b6
Author: thomasv <thomasv@gitorious>
Date:   Wed,  4 Sep 2013 16:46:27 +0200

move transaction code and fix issue #280

Diffstat:
Mgui/gui_classic.py | 2+-
Mlib/__init__.py | 3++-
Mlib/bitcoin.py | 294-------------------------------------------------------------------------------
Dlib/deserialize.py | 392-------------------------------------------------------------------------------
Alib/transaction.py | 715+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/wallet.py | 37++++++++++++++++++++++++++-----------
Mplugins/qrscanner.py | 3++-
Msetup.py | 2+-
8 files changed, 747 insertions(+), 701 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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('<h') - def read_uint16(self): return self._read_num('<H') - def read_int32(self): return self._read_num('<i') - def read_uint32(self): return self._read_num('<I') - def read_int64(self): return self._read_num('<q') - def read_uint64(self): return self._read_num('<Q') - - def write_boolean(self, val): return self.write(chr(1) if val else chr(0)) - def write_int16(self, val): return self._write_num('<h', val) - def write_uint16(self, val): return self._write_num('<H', val) - def write_int32(self, val): return self._write_num('<i', val) - def write_uint32(self, val): return self._write_num('<I', val) - def write_int64(self, val): return self._write_num('<q', val) - def write_uint64(self, val): return self._write_num('<Q', val) - - def read_compact_size(self): - size = ord(self.input[self.read_cursor]) - self.read_cursor += 1 - if size == 253: - size = self._read_num('<H') - elif size == 254: - size = self._read_num('<I') - elif size == 255: - size = self._read_num('<Q') - return size - - def write_compact_size(self, size): - if size < 0: - raise SerializationError("attempt to write size < 0") - elif size < 253: - self.write(chr(size)) - elif size < 2**16: - self.write('\xfd') - self._write_num('<H', size) - elif size < 2**32: - self.write('\xfe') - self._write_num('<I', size) - elif size < 2**64: - self.write('\xff') - self._write_num('<Q', size) - - def _read_num(self, format): - (i,) = struct.unpack_from(format, self.input, self.read_cursor) - self.read_cursor += struct.calcsize(format) - return i - - def _write_num(self, format, num): - 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): - pass - -class Enumeration: - def __init__(self, name, enumList): - self.__doc__ = name - lookup = { } - reverseLookup = { } - i = 0 - uniqueNames = [ ] - uniqueValues = [ ] - for x in enumList: - if type(x) == types.TupleType: - 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 x in uniqueNames: - raise EnumException, "enum name is not unique: " + x - if i in uniqueValues: - raise EnumException, "enum value is not unique for " + x - uniqueNames.append(x) - uniqueValues.append(i) - lookup[x] = i - reverseLookup[i] = x - i = i + 1 - self.lookup = lookup - self.reverseLookup = reverseLookup - def __getattr__(self, attr): - if not self.lookup.has_key(attr): - raise AttributeError - return self.lookup[attr] - def whatis(self, value): - return self.reverseLookup[value] - - -# This function comes from bitcointools, bct-LICENSE.txt. -def long_hex(bytes): - return bytes.encode('hex_codec') - -# This function comes from bitcointools, bct-LICENSE.txt. -def short_hex(bytes): - t = bytes.encode('hex_codec') - if len(t) < 11: - return t - return t[0:4]+"..."+t[-4:] - - - -def parse_TxIn(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_TxOut(vds, i): - d = {} - d['value'] = vds.read_int64() - scriptPubKey = vds.read_bytes(vds.read_compact_size()) - d['address'] = get_address_from_output_script(scriptPubKey) - d['raw_output_script'] = scriptPubKey.encode('hex') - d['index'] = i - return d - - -def parse_Transaction(vds): - 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_TxIn(vds)) - n_vout = vds.read_compact_size() - d['outputs'] = [] - for i in xrange(n_vout): - d['outputs'].append(parse_TxOut(vds, i)) - d['lockTime'] = vds.read_uint32() - return d - -def parse_redeemScript(bytes): - dec = [ x for x in script_GetOp(bytes.decode('hex')) ] - - # 2 of 2 - match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] - if match_decoded(dec, match): - pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex') ] - return 2, pubkeys - - # 2 of 3 - match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] - if match_decoded(dec, match): - pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex'), dec[3][1].encode('hex') ] - return 2, pubkeys - - - -opcodes = Enumeration("Opcodes", [ - ("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED", - "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7", - "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16", - "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY", - "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP", - "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT", - "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", "OP_AND", - "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL", - "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV", - "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR", - "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN", - "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX", - "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", - "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", - "OP_CHECKMULTISIGVERIFY", - ("OP_SINGLEBYTE_END", 0xF0), - ("OP_DOUBLEBYTE_BEGIN", 0xF000), - "OP_PUBKEY", "OP_PUBKEYHASH", - ("OP_INVALIDOPCODE", 0xFFFF), -]) - - -def script_GetOp(bytes): - i = 0 - while i < len(bytes): - vch = None - opcode = ord(bytes[i]) - i += 1 - if opcode >= 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('<H', bytes, i) - i += 2 - elif opcode == opcodes.OP_PUSHDATA4: - (nSize,) = struct.unpack_from('<I', bytes, i) - i += 4 - vch = bytes[i:i+nSize] - i += nSize - - yield (opcode, vch, i) - - -def script_GetOpName(opcode): - return (opcodes.whatis(opcode)).replace("OP_", "") - - -def decode_script(bytes): - result = '' - for (opcode, vch, i) in script_GetOp(bytes): - if len(result) > 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 @@ -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 <http://www.gnu.org/licenses/>. + + +# 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('<h') + def read_uint16(self): return self._read_num('<H') + def read_int32(self): return self._read_num('<i') + def read_uint32(self): return self._read_num('<I') + def read_int64(self): return self._read_num('<q') + def read_uint64(self): return self._read_num('<Q') + + def write_boolean(self, val): return self.write(chr(1) if val else chr(0)) + def write_int16(self, val): return self._write_num('<h', val) + def write_uint16(self, val): return self._write_num('<H', val) + def write_int32(self, val): return self._write_num('<i', val) + def write_uint32(self, val): return self._write_num('<I', val) + def write_int64(self, val): return self._write_num('<q', val) + def write_uint64(self, val): return self._write_num('<Q', val) + + def read_compact_size(self): + size = ord(self.input[self.read_cursor]) + self.read_cursor += 1 + if size == 253: + size = self._read_num('<H') + elif size == 254: + size = self._read_num('<I') + elif size == 255: + size = self._read_num('<Q') + return size + + def write_compact_size(self, size): + if size < 0: + raise SerializationError("attempt to write size < 0") + elif size < 253: + self.write(chr(size)) + elif size < 2**16: + self.write('\xfd') + self._write_num('<H', size) + elif size < 2**32: + self.write('\xfe') + self._write_num('<I', size) + elif size < 2**64: + self.write('\xff') + self._write_num('<Q', size) + + def _read_num(self, format): + (i,) = struct.unpack_from(format, self.input, self.read_cursor) + self.read_cursor += struct.calcsize(format) + return i + + def _write_num(self, format, num): + 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): + pass + +class Enumeration: + def __init__(self, name, enumList): + self.__doc__ = name + lookup = { } + reverseLookup = { } + i = 0 + uniqueNames = [ ] + uniqueValues = [ ] + for x in enumList: + if type(x) == types.TupleType: + 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 x in uniqueNames: + raise EnumException, "enum name is not unique: " + x + if i in uniqueValues: + raise EnumException, "enum value is not unique for " + x + uniqueNames.append(x) + uniqueValues.append(i) + lookup[x] = i + reverseLookup[i] = x + i = i + 1 + self.lookup = lookup + self.reverseLookup = reverseLookup + def __getattr__(self, attr): + if not self.lookup.has_key(attr): + raise AttributeError + return self.lookup[attr] + def whatis(self, value): + return self.reverseLookup[value] + + +# This function comes from bitcointools, bct-LICENSE.txt. +def long_hex(bytes): + return bytes.encode('hex_codec') + +# This function comes from bitcointools, bct-LICENSE.txt. +def short_hex(bytes): + t = bytes.encode('hex_codec') + if len(t) < 11: + return t + return t[0:4]+"..."+t[-4:] + + + + +def parse_redeemScript(bytes): + dec = [ x for x in script_GetOp(bytes.decode('hex')) ] + + # 2 of 2 + match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec, match): + pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex') ] + return 2, pubkeys + + # 2 of 3 + match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec, match): + pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex'), dec[3][1].encode('hex') ] + return 2, pubkeys + + + +opcodes = Enumeration("Opcodes", [ + ("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED", + "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7", + "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16", + "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY", + "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP", + "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT", + "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", "OP_AND", + "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL", + "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV", + "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR", + "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN", + "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX", + "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", + "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", + "OP_CHECKMULTISIGVERIFY", + ("OP_SINGLEBYTE_END", 0xF0), + ("OP_DOUBLEBYTE_BEGIN", 0xF000), + "OP_PUBKEY", "OP_PUBKEYHASH", + ("OP_INVALIDOPCODE", 0xFFFF), +]) + + +def script_GetOp(bytes): + i = 0 + while i < len(bytes): + vch = None + opcode = ord(bytes[i]) + i += 1 + if opcode >= 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('<H', bytes, i) + i += 2 + elif opcode == opcodes.OP_PUSHDATA4: + (nSize,) = struct.unpack_from('<I', bytes, i) + i += 4 + vch = bytes[i:i+nSize] + i += nSize + + yield (opcode, vch, i) + + +def script_GetOpName(opcode): + return (opcodes.whatis(opcode)).replace("OP_", "") + + +def decode_script(bytes): + result = '' + for (opcode, vch, i) in script_GetOp(bytes): + if len(result) > 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 @@ -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 @@ -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 @@ -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',