Browse Source

Extend transaction serialization, format to handle unsigned inputs where only the address is known, the public key is unknown.

283
ThomasV 10 years ago
parent
commit
16f36ee6e2
  1. 77
      lib/transaction.py
  2. 114
      lib/wallet.py

77
lib/transaction.py

@ -332,9 +332,16 @@ def parse_xpub(x_pubkey):
from account import OldAccount
mpk, s = OldAccount.parse_xpubkey(x_pubkey)
pubkey = OldAccount.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1])
elif x_pubkey[0:2] == 'fd':
addrtype = ord(x_pubkey[2:4].decode('hex'))
hash160 = x_pubkey[4:].decode('hex')
pubkey = None
address = hash_160_to_bc_address(hash160, addrtype)
else:
raise BaseException("Cannnot parse pubkey")
return pubkey
if pubkey:
address = public_key_to_bc_address(pubkey.decode('hex'))
return pubkey, address
def parse_scriptSig(d, bytes):
@ -365,7 +372,7 @@ def parse_scriptSig(d, bytes):
x_pubkey = decoded[1][1].encode('hex')
try:
signatures = parse_sig([sig])
pubkey = parse_xpub(x_pubkey)
pubkey, address = parse_xpub(x_pubkey)
except:
import traceback
traceback.print_exc(file=sys.stdout)
@ -375,7 +382,7 @@ def parse_scriptSig(d, bytes):
d['x_pubkeys'] = [x_pubkey]
d['num_sig'] = 1
d['pubkeys'] = [pubkey]
d['address'] = public_key_to_bc_address(pubkey.decode('hex'))
d['address'] = address
return
# p2sh transaction, 2 of n
@ -639,7 +646,11 @@ class Transaction:
sig_list = ''.join( map( lambda x: push_script(x), sig_list))
if not p2sh:
script = sig_list
script += push_script(pubkeys[0])
x_pubkey = pubkeys[0]
if x_pubkey is None:
addrtype, h160 = bc_address_to_hash_160(txin['address'])
x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex')
script += push_script(x_pubkey)
else:
script = '00' # op_0
script += sig_list
@ -673,16 +684,6 @@ class Transaction:
def hash(self):
return Hash(self.raw.decode('hex') )[::-1].encode('hex')
def add_signature(self, i, pubkey, sig):
print_error("adding signature for", pubkey)
txin = self.inputs[i]
pubkeys = txin['pubkeys']
ii = pubkeys.index(pubkey)
txin['signatures'][ii] = sig
txin['x_pubkeys'][ii] = pubkey
self.inputs[i] = txin
self.raw = self.serialize()
def add_input(self, input):
self.inputs.append(input)
self.raw = None
@ -707,66 +708,56 @@ class Transaction:
r += txin['num_sig']
return s, r
def is_complete(self):
s, r = self.signature_count()
return r == s
def inputs_to_sign(self):
from account import BIP32_Account, OldAccount
xpub_list = []
addr_list = set()
out = set()
for txin in self.inputs:
x_signatures = txin['signatures']
signatures = filter(lambda x: x is not None, x_signatures)
if len(signatures) == txin['num_sig']:
# input is complete
continue
for k, x_pubkey in enumerate(txin['x_pubkeys']):
if x_signatures[k] is not None:
# this pubkey already signed
continue
if x_pubkey[0:2] == 'ff':
xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
xpub_list.append((xpub,sequence))
elif x_pubkey[0:2] == 'fe':
xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
xpub_list.append((xpub,sequence))
else:
addr_list.add(txin['address'])
return addr_list, xpub_list
out.add(x_pubkey)
return out
def sign(self, keypairs):
print_error("tx.sign(), keypairs:", keypairs)
for i, txin in enumerate(self.inputs):
# continue if this txin is complete
signatures = filter(lambda x: x is not None, txin['signatures'])
num = txin['num_sig']
if len(signatures) == num:
# continue if this txin is complete
continue
redeem_pubkeys = txin['pubkeys']
for_sig = Hash(self.tx_for_sig(i).decode('hex'))
for pubkey in redeem_pubkeys:
if pubkey in keypairs.keys():
for x_pubkey in txin['x_pubkeys']:
if x_pubkey in keypairs.keys():
print_error("adding signature for", x_pubkey)
# add pubkey to txin
txin = self.inputs[i]
x_pubkeys = txin['x_pubkeys']
ii = x_pubkeys.index(x_pubkey)
sec = keypairs[x_pubkey]
pubkey = public_key_from_private_key(sec)
txin['x_pubkeys'][ii] = pubkey
txin['pubkeys'][ii] = pubkey
self.inputs[i] = txin
# add signature
sec = keypairs[pubkey]
for_sig = Hash(self.tx_for_sig(i).decode('hex'))
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_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der )
assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der)
self.add_signature(i, pubkey, sig.encode('hex'))
txin['signatures'][ii] = sig.encode('hex')
self.inputs[i] = txin
print_error("is_complete", self.is_complete())
self.raw = self.serialize()

114
lib/wallet.py

@ -766,35 +766,15 @@ class Abstract_Wallet(object):
return
# check that the password is correct. This will raise if it's not.
self.check_password(password)
keypairs = {}
# tx.inputs_to_sign() : return list of addresses or derivations
# this list should be enriched by add_keypairs
addr_list, xpub_list = tx.inputs_to_sign()
for addr in addr_list:
if self.is_mine(addr):
private_keys = self.get_private_key(addr, password)
for sec in private_keys:
pubkey = public_key_from_private_key(sec)
keypairs[ pubkey ] = sec
for xpub, sequence in xpub_list:
# look for account that can sign
for k, account in self.accounts.items():
if xpub in account.get_master_pubkeys():
break
else:
continue
pk = account.get_private_key(sequence, self, password)
for sec in pk:
pubkey = public_key_from_private_key(sec)
keypairs[pubkey] = sec
x_pubkeys = tx.inputs_to_sign()
for x in x_pubkeys:
sec = self.get_private_key_from_xpubkey(x, password)
print "sec", sec
if sec:
keypairs[ x ] = sec
if keypairs:
tx.sign(keypairs)
run_hook('sign_transaction', tx, password)
def sendtx(self, tx):
@ -1013,7 +993,59 @@ class Abstract_Wallet(object):
return age > age_limit
def can_sign(self, tx):
pass
if self.is_watching_only():
return False
if tx.is_complete():
return False
for x in tx.inputs_to_sign():
if self.can_sign_xpubkey(x):
return True
return False
def get_private_key_from_xpubkey(self, x_pubkey, password):
if x_pubkey[0:2] in ['02','03','04']:
addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
if self.is_mine(addr):
return self.get_private_key(addr, password)[0]
elif x_pubkey[0:2] == 'ff':
xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
for k, account in self.accounts.items():
if xpub in account.get_master_pubkeys():
pk = account.get_private_key(sequence, self, password)
return pk[0]
elif x_pubkey[0:2] == 'fe':
xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
for k, account in self.accounts.items():
if xpub in account.get_master_pubkeys():
pk = account.get_private_key(sequence, self, password)
return pk[0]
elif x_pubkey[0:2] == 'fd':
addrtype = ord(x_pubkey[2:4].decode('hex'))
addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
if self.is_mine(addr):
return self.get_private_key(addr, password)[0]
else:
raise BaseException("z")
def can_sign_xpubkey(self, x_pubkey):
if x_pubkey[0:2] in ['02','03','04']:
addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
return self.is_mine(addr)
elif x_pubkey[0:2] == 'ff':
xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
elif x_pubkey[0:2] == 'fe':
xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
return xpub == self.get_master_public_key()
elif x_pubkey[0:2] == 'fd':
addrtype = ord(x_pubkey[2:4].decode('hex'))
addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
return self.is_mine(addr)
else:
raise BaseException("z")
def is_watching_only(self):
False
@ -1255,21 +1287,6 @@ class BIP32_Wallet(Deterministic_Wallet):
xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
return xpub, xprv
def can_sign(self, tx):
if self.is_watching_only():
return False
if tx.is_complete():
return False
addr_list, xpub_list = tx.inputs_to_sign()
for addr in addr_list:
if self.is_mine(addr):
return True
mpk = [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
for xpub, sequence in xpub_list:
if xpub in mpk:
return True
return False
def create_master_keys(self, password):
seed = self.get_seed(password)
self.add_cosigner_seed(seed, self.root_name, password)
@ -1564,19 +1581,6 @@ class OldWallet(Deterministic_Wallet):
s = self.get_seed(password)
return ' '.join(old_mnemonic.mn_encode(s))
def can_sign(self, tx):
if self.is_watching_only():
return False
if tx.is_complete():
return False
addr_list, xpub_list = tx.inputs_to_sign()
for addr in addr_list:
if self.is_mine(addr):
return True
for xpub, sequence in xpub_list:
if xpub == self.get_master_public_key():
return True
return False

Loading…
Cancel
Save