mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
Extend transaction serialization, format to handle unsigned inputs where only the address is known, the public key is unknown.
This commit is contained in:
parent
c4ce16e2b6
commit
16f36ee6e2
2 changed files with 93 additions and 98 deletions
|
@ -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
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…
Add table
Reference in a new issue