move transaction code and fix issue #280
This commit is contained in:
294
lib/bitcoin.py
294
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):
|
||||
|
||||
Reference in New Issue
Block a user