commit ea7718fc59fa7111d235ede8a48543d2a7dbfb10
parent b4bb3c74499520d66a98271e9e011558abf5be83
Author: thomasv <thomasv@gitorious>
Date: Thu, 21 Feb 2013 14:18:12 +0100
create Transaction class
Diffstat:
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
-