electrum

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

commit ea7718fc59fa7111d235ede8a48543d2a7dbfb10
parent b4bb3c74499520d66a98271e9e011558abf5be83
Author: thomasv <thomasv@gitorious>
Date:   Thu, 21 Feb 2013 14:18:12 +0100

create Transaction class

Diffstat:
Melectrum | 50++++++++++++++++++++++++++++----------------------
Mlib/__init__.py | 1+
Mlib/bitcoin.py | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mlib/wallet.py | 51+++++++++++++++++----------------------------------
4 files changed, 102 insertions(+), 65 deletions(-)

diff --git a/electrum b/electrum @@ -711,38 +711,44 @@ if __name__ == '__main__': import ast inputs = ast.literal_eval(args[1]) outputs = ast.literal_eval(args[2]) - inputs = map(lambda x: (None, None, x["txid"], x["vout"], None, None), inputs) - outputs = map(lambda x: (x[0],int(x[1]*1e8)), outputs.items()) - tx = raw_tx(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign + # convert to own format + for i in inputs: + i['tx_hash'] = i['txid'] + i['index'] = i['vout'] + outputs = map(lambda x: (x[0],int(1e8*x[1])), outputs.items()) + tx = Transaction.from_io(inputs, outputs) print_msg( tx ) elif cmd == 'decoderawtransaction': - print_json( bitcoin.deserialize(args[1]) ) + tx = Transaction(args[1]) + print_json( tx.deserialize() ) elif cmd == 'signrawtransaction': - d = bitcoin.deserialize(args[1]) + tx = Transaction(args[1]) txouts = args[2] if len(args)>2 else [] - private_keys = args[3] if len(args)>3 else [] - - inputs = [] - for x in d['inputs']: - txid = x["prevout_hash"] - nout = x["prevout_n"] - tx = wallet.transactions.get(txid) - txout = tx['outputs'][nout] - addr = txout['address'] - v = txout['value'] - inputs.append( (addr, v, txid, nout, txout['raw_output_script'], [(None,None)] ) ) - - outputs = map(lambda x: (x['address'],x['value']), d['outputs']) - print_error("inputs", inputs) - print_error("outputs", outputs) - - tx = wallet.signed_tx( inputs, outputs, password ) + private_keys = args[3] if len(args)>3 else {} + + if not private_keys: + for txin in tx.inputs: + txid = txin["prevout_hash"] + index = txin["prevout_n"] + utx = wallet.transactions.get(txid) + txout = utx['outputs'][index] + txin['address'] = txout['address'] + txin['raw_output_script'] = txout['raw_output_script'] + # convert to own format + txin['tx_hash'] = txin['prevout_hash'] + txin['index'] = txin['prevout_n'] + secexp, compressed = wallet.get_private_key(txin['address'], password) + private_keys[addr] = (secexp,compressed) + + tx.sign( private_keys ) print_msg(tx) if cmd not in offline_commands and not options.offline: synchronizer.stop() + + diff --git a/lib/__init__.py b/lib/__init__.py @@ -7,5 +7,6 @@ from verifier import WalletVerifier from interface import Interface, pick_random_server, DEFAULT_SERVERS from simple_config import SimpleConfig import bitcoin +from bitcoin import Transaction from mnemonic import mn_encode as mnemonic_encode from mnemonic import mn_decode as mnemonic_decode diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -317,12 +317,14 @@ def tx_filter(s): return out def raw_tx( 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)): - _, _, p_hash, p_index, p_script, pubkeysig = inputs[i] - s += p_hash.decode('hex')[::-1].encode('hex') # prev hash - s += int_to_hex(p_index,4) # prev index + txin = inputs[i] + pubkeysig = txin.get('pubkeysig',[]) + 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: if len(pubkeysig) == 1: @@ -350,7 +352,7 @@ def raw_tx( inputs, outputs, for_sig = None ): if len(pubkeysig) > 1: script = multisig_script(pubkeysig) # p2sh uses the inner script else: - script = p_script # scriptsig + script = txin['raw_output_script'] # scriptsig else: script='' s += var_int( len(tx_filter(script))/2 ) # script length @@ -382,11 +384,6 @@ def raw_tx( inputs, outputs, for_sig = None ): return tx_filter(s) -def deserialize(raw_tx): - import deserialize - vds = deserialize.BCDataStream() - vds.write(raw_tx.decode('hex')) - return deserialize.parse_Transaction(vds) def multisig_script(public_keys, num=None): @@ -419,6 +416,56 @@ def multisig_script(public_keys, num=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) + + @classmethod + def from_io(klass, inputs, outputs): + raw = raw_tx(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign + self = klass(raw) + self.inputs = inputs + self.outputs = outputs + return self + + def __str__(self): + return self.raw + + def hash(self): + return Hash(self.raw.decode('hex') )[::-1].encode('hex') + + def sign(self, private_keys): + + for i in range(len(self.inputs)): + txin = self.inputs[i] + secexp, compressed = private_keys[txin['address']] + private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + public_key = private_key.get_verifying_key() + pkey = EC_KEY(secexp) + pubkey = GetPubKey(pkey.pubkey, compressed) + tx = raw_tx( self.inputs, self.outputs, for_sig = i ) + sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) + assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) + self.inputs[i]["pubkeysig"] = [(pubkey, sig)] + + self.raw = raw_tx( 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 test_bip32(): seed = "ff000000000000000000000000000000".decode('hex') master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed) diff --git a/lib/wallet.py b/lib/wallet.py @@ -210,7 +210,7 @@ class Wallet: n = self.change_addresses.index(address) for_change = True else: - raise BaseException("unknown address") + raise BaseException("unknown address", address) seed = self.pw_decode( self.seed, password) if not seed: return None @@ -609,7 +609,8 @@ class Wallet: addr = item.get('address') v = item.get('value') total += v - inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], [(None,None)] )) + item['pubkeysig'] = [(None, None)] + inputs.append( item ) fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee if total >= amount + fee: break else: @@ -628,22 +629,6 @@ class Wallet: outputs[posn:posn] = [( change_addr, change_amount)] return outputs - def sign_inputs( self, inputs, outputs, password ): - s_inputs = [] - for i in range(len(inputs)): - addr, v, p_hash, p_pos, p_scriptPubKey, _ = inputs[i] - secexp, compressed = self.get_private_key(addr, password) - private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - public_key = private_key.get_verifying_key() - - pkey = EC_KEY(secexp) - pubkey = GetPubKey(pkey.pubkey, compressed) - - tx = raw_tx( inputs, outputs, for_sig = i ) - sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) - s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, [(pubkey, sig)] ) ) - return s_inputs def pw_encode(self, s, password): if password: @@ -823,7 +808,7 @@ class Wallet: raise ValueError("Not enough funds") if not self.use_change and not change_addr: - change_addr = inputs[-1][0] + change_addr = inputs[-1]['address'] print_error( "Sending change to", change_addr ) outputs = self.add_tx_change(outputs, amount, fee, total, change_addr) @@ -843,9 +828,14 @@ class Wallet: return tx def signed_tx(self, inputs, outputs, password): - s_inputs = self.sign_inputs( inputs, outputs, password ) - tx = raw_tx( s_inputs, outputs ) - return tx + tx = Transaction.from_io(inputs, outputs) + private_keys = {} + for txin in tx.inputs: + addr = txin['address'] + secexp, compressed = self.get_private_key(addr, password) + private_keys[addr] = (secexp,compressed) + tx.sign(private_keys) + return str(tx) def sendtx(self, tx): # synchronous @@ -1344,7 +1334,11 @@ class WalletSynchronizer(threading.Thread): elif method == 'blockchain.transaction.get': tx_hash = params[0] tx_height = params[1] - d = self.deserialize_tx(tx_hash, tx_height, result) + assert tx_hash == hash_encode(Hash(result.decode('hex'))) + tx = Transaction(result) + d = tx.deserialize() + d['height'] = tx_height + d['tx_hash'] = tx_hash self.wallet.receive_tx_callback(tx_hash, d) self.was_updated = True requested_tx.remove( (tx_hash, tx_height) ) @@ -1365,14 +1359,3 @@ class WalletSynchronizer(threading.Thread): self.was_updated = False - def deserialize_tx(self, tx_hash, tx_height, raw_tx): - - assert tx_hash == hash_encode(Hash(raw_tx.decode('hex'))) - import deserialize - vds = deserialize.BCDataStream() - vds.write(raw_tx.decode('hex')) - d = deserialize.parse_Transaction(vds) - d['height'] = tx_height - d['tx_hash'] = tx_hash - return d -