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

114
lib/wallet.py

@ -766,35 +766,15 @@ class Abstract_Wallet(object):
return return
# check that the password is correct. This will raise if it's not. # check that the password is correct. This will raise if it's not.
self.check_password(password) self.check_password(password)
keypairs = {} keypairs = {}
x_pubkeys = tx.inputs_to_sign()
# tx.inputs_to_sign() : return list of addresses or derivations for x in x_pubkeys:
# this list should be enriched by add_keypairs sec = self.get_private_key_from_xpubkey(x, password)
addr_list, xpub_list = tx.inputs_to_sign() print "sec", sec
for addr in addr_list: if sec:
if self.is_mine(addr): keypairs[ x ] = sec
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
if keypairs: if keypairs:
tx.sign(keypairs) tx.sign(keypairs)
run_hook('sign_transaction', tx, password) run_hook('sign_transaction', tx, password)
def sendtx(self, tx): def sendtx(self, tx):
@ -1013,7 +993,59 @@ class Abstract_Wallet(object):
return age > age_limit return age > age_limit
def can_sign(self, tx): 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): def is_watching_only(self):
False False
@ -1255,21 +1287,6 @@ class BIP32_Wallet(Deterministic_Wallet):
xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
return xpub, xprv 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): def create_master_keys(self, password):
seed = self.get_seed(password) seed = self.get_seed(password)
self.add_cosigner_seed(seed, self.root_name, password) self.add_cosigner_seed(seed, self.root_name, password)
@ -1564,19 +1581,6 @@ class OldWallet(Deterministic_Wallet):
s = self.get_seed(password) s = self.get_seed(password)
return ' '.join(old_mnemonic.mn_encode(s)) 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