Fix can_sign and cold storage
This commit is contained in:
parent
abeb781879
commit
b1b15f510c
|
@ -49,6 +49,32 @@ class KeyStore(PrintError):
|
||||||
def can_import(self):
|
def can_import(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_tx_derivations(self, tx):
|
||||||
|
keypairs = {}
|
||||||
|
for txin in tx.inputs():
|
||||||
|
num_sig = txin.get('num_sig')
|
||||||
|
if num_sig is None:
|
||||||
|
continue
|
||||||
|
x_signatures = txin['signatures']
|
||||||
|
signatures = filter(None, x_signatures)
|
||||||
|
if len(signatures) == 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
|
||||||
|
derivation = self.get_pubkey_derivation(x_pubkey)
|
||||||
|
if not derivation:
|
||||||
|
continue
|
||||||
|
keypairs[x_pubkey] = derivation
|
||||||
|
return keypairs
|
||||||
|
|
||||||
|
def can_sign(self, tx):
|
||||||
|
if self.is_watching_only():
|
||||||
|
return False
|
||||||
|
return bool(self.get_tx_derivations(tx))
|
||||||
|
|
||||||
|
|
||||||
class Software_KeyStore(KeyStore):
|
class Software_KeyStore(KeyStore):
|
||||||
|
|
||||||
|
@ -70,32 +96,15 @@ class Software_KeyStore(KeyStore):
|
||||||
decrypted = ec.decrypt_message(message)
|
decrypted = ec.decrypt_message(message)
|
||||||
return decrypted
|
return decrypted
|
||||||
|
|
||||||
def get_keypairs_for_sig(self, tx, password):
|
|
||||||
keypairs = {}
|
|
||||||
for txin in tx.inputs():
|
|
||||||
num_sig = txin.get('num_sig')
|
|
||||||
if num_sig is None:
|
|
||||||
continue
|
|
||||||
x_signatures = txin['signatures']
|
|
||||||
signatures = filter(None, x_signatures)
|
|
||||||
if len(signatures) == 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
|
|
||||||
derivation = txin['derivation']
|
|
||||||
sec = self.get_private_key(derivation, password)
|
|
||||||
if sec:
|
|
||||||
keypairs[x_pubkey] = sec
|
|
||||||
return keypairs
|
|
||||||
|
|
||||||
def sign_transaction(self, tx, password):
|
def sign_transaction(self, tx, password):
|
||||||
|
if self.is_watching_only():
|
||||||
|
return
|
||||||
# Raise if password is not correct.
|
# Raise if password is not correct.
|
||||||
self.check_password(password)
|
self.check_password(password)
|
||||||
# Add private keys
|
# Add private keys
|
||||||
keypairs = self.get_keypairs_for_sig(tx, password)
|
keypairs = self.get_tx_derivations(tx)
|
||||||
|
for k, v in keypairs.items():
|
||||||
|
keypairs[k] = self.get_private_key(v, password)
|
||||||
# Sign
|
# Sign
|
||||||
if keypairs:
|
if keypairs:
|
||||||
tx.sign(keypairs)
|
tx.sign(keypairs)
|
||||||
|
@ -157,13 +166,19 @@ class Imported_KeyStore(Software_KeyStore):
|
||||||
def get_private_key(self, sequence, password):
|
def get_private_key(self, sequence, password):
|
||||||
for_change, i = sequence
|
for_change, i = sequence
|
||||||
assert for_change == 0
|
assert for_change == 0
|
||||||
pubkey = (self.change_pubkeys if for_change else self.receiving_pubkeys)[i]
|
pubkey = self.receiving_pubkeys[i]
|
||||||
pk = pw_decode(self.keypairs[pubkey], password)
|
pk = pw_decode(self.keypairs[pubkey], password)
|
||||||
# this checks the password
|
# this checks the password
|
||||||
if pubkey != public_key_from_private_key(pk):
|
if pubkey != public_key_from_private_key(pk):
|
||||||
raise InvalidPassword()
|
raise InvalidPassword()
|
||||||
return pk
|
return pk
|
||||||
|
|
||||||
|
def get_pubkey_derivation(self, pubkey):
|
||||||
|
if pubkey not in self.receiving_keys:
|
||||||
|
return
|
||||||
|
i = self.receiving_keys.index(pubkey)
|
||||||
|
return (False, i)
|
||||||
|
|
||||||
def update_password(self, old_password, new_password):
|
def update_password(self, old_password, new_password):
|
||||||
if old_password is not None:
|
if old_password is not None:
|
||||||
self.check_password(old_password)
|
self.check_password(old_password)
|
||||||
|
@ -255,6 +270,14 @@ class Xpub:
|
||||||
assert len(s) == 2
|
assert len(s) == 2
|
||||||
return xkey, s
|
return xkey, s
|
||||||
|
|
||||||
|
def get_pubkey_derivation(self, x_pubkey):
|
||||||
|
if x_pubkey[0:2] != 'ff':
|
||||||
|
return
|
||||||
|
xpub, derivation = self.parse_xpubkey(x_pubkey)
|
||||||
|
if self.xpub != xpub:
|
||||||
|
return
|
||||||
|
return derivation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
|
@ -301,7 +324,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
def is_watching_only(self):
|
def is_watching_only(self):
|
||||||
return self.xprv is None
|
return self.xprv is None
|
||||||
|
|
||||||
|
|
||||||
def get_mnemonic(self, password):
|
def get_mnemonic(self, password):
|
||||||
return self.get_seed(password)
|
return self.get_seed(password)
|
||||||
|
|
||||||
|
@ -314,9 +336,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
|
xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
|
||||||
self.add_xprv(xprv)
|
self.add_xprv(xprv)
|
||||||
|
|
||||||
def can_sign(self, xpub):
|
|
||||||
return xpub == self.xpub and self.xprv is not None
|
|
||||||
|
|
||||||
def get_private_key(self, sequence, password):
|
def get_private_key(self, sequence, password):
|
||||||
xprv = self.get_master_private_key(password)
|
xprv = self.get_master_private_key(password)
|
||||||
_, _, _, c, k = deserialize_xkey(xprv)
|
_, _, _, c, k = deserialize_xkey(xprv)
|
||||||
|
@ -324,6 +343,7 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
return pk
|
return pk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Old_KeyStore(Deterministic_KeyStore):
|
class Old_KeyStore(Deterministic_KeyStore):
|
||||||
|
|
||||||
def __init__(self, d):
|
def __init__(self, d):
|
||||||
|
@ -430,8 +450,7 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||||
|
|
||||||
def get_xpubkey(self, for_change, n):
|
def get_xpubkey(self, for_change, n):
|
||||||
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
|
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
|
||||||
x_pubkey = 'fe' + self.mpk + s
|
return 'fe' + self.mpk + s
|
||||||
return x_pubkey
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_xpubkey(self, x_pubkey):
|
def parse_xpubkey(self, x_pubkey):
|
||||||
|
@ -447,6 +466,14 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||||
assert len(s) == 2
|
assert len(s) == 2
|
||||||
return mpk, s
|
return mpk, s
|
||||||
|
|
||||||
|
def get_pubkey_derivation(self, x_pubkey):
|
||||||
|
if x_pubkey[0:2] != 'fe':
|
||||||
|
return
|
||||||
|
mpk, derivation = self.parse_xpubkey(x_pubkey)
|
||||||
|
if self.mpk != mpk:
|
||||||
|
return
|
||||||
|
return derivation
|
||||||
|
|
||||||
def update_password(self, old_password, new_password):
|
def update_password(self, old_password, new_password):
|
||||||
if old_password is not None:
|
if old_password is not None:
|
||||||
self.check_password(old_password)
|
self.check_password(old_password)
|
||||||
|
@ -550,7 +577,7 @@ def xpubkey_to_address(x_pubkey):
|
||||||
pubkey = BIP32_KeyStore.derive_pubkey_from_xpub(xpub, s[0], s[1])
|
pubkey = BIP32_KeyStore.derive_pubkey_from_xpub(xpub, s[0], s[1])
|
||||||
elif x_pubkey[0:2] == 'fe':
|
elif x_pubkey[0:2] == 'fe':
|
||||||
mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
|
mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
|
||||||
pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1])
|
pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
|
||||||
elif x_pubkey[0:2] == 'fd':
|
elif x_pubkey[0:2] == 'fd':
|
||||||
addrtype = ord(x_pubkey[2:4].decode('hex'))
|
addrtype = ord(x_pubkey[2:4].decode('hex'))
|
||||||
hash160 = x_pubkey[4:].decode('hex')
|
hash160 = x_pubkey[4:].decode('hex')
|
||||||
|
|
|
@ -981,28 +981,21 @@ class Abstract_Wallet(PrintError):
|
||||||
address = txin['address']
|
address = txin['address']
|
||||||
if self.is_mine(address):
|
if self.is_mine(address):
|
||||||
self.add_input_sig_info(txin, address)
|
self.add_input_sig_info(txin, address)
|
||||||
else:
|
|
||||||
txin['can_sign'] = False
|
|
||||||
|
|
||||||
def can_sign(self, tx):
|
def can_sign(self, tx):
|
||||||
if self.is_watching_only():
|
|
||||||
return False
|
|
||||||
if tx.is_complete():
|
if tx.is_complete():
|
||||||
return False
|
return False
|
||||||
## add input info. (should be done already)
|
for k in self.get_keystores():
|
||||||
#for txin in tx.inputs():
|
if k.can_sign(tx):
|
||||||
# self.add_input_info(txin)
|
return True
|
||||||
can_sign = any([txin['can_sign'] for txin in tx.inputs()])
|
|
||||||
return can_sign
|
|
||||||
|
|
||||||
def get_input_tx(self, tx_hash):
|
def get_input_tx(self, tx_hash):
|
||||||
# First look up an input transaction in the wallet where it
|
# First look up an input transaction in the wallet where it
|
||||||
# will likely be. If co-signing a transaction it may not have
|
# will likely be. If co-signing a transaction it may not have
|
||||||
# all the input txs, in which case we ask the network.
|
# all the input txs, in which case we ask the network.
|
||||||
tx = self.transactions.get(tx_hash)
|
tx = self.transactions.get(tx_hash)
|
||||||
if not tx:
|
if not tx and self.network:
|
||||||
request = ('blockchain.transaction.get', [tx_hash])
|
request = ('blockchain.transaction.get', [tx_hash])
|
||||||
# FIXME: what if offline?
|
|
||||||
tx = Transaction(self.network.synchronous_get(request))
|
tx = Transaction(self.network.synchronous_get(request))
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
@ -1014,7 +1007,6 @@ class Abstract_Wallet(PrintError):
|
||||||
for txin in tx.inputs():
|
for txin in tx.inputs():
|
||||||
tx_hash = txin['prevout_hash']
|
tx_hash = txin['prevout_hash']
|
||||||
txin['prev_tx'] = self.get_input_tx(tx_hash)
|
txin['prev_tx'] = self.get_input_tx(tx_hash)
|
||||||
# I should add the address index if it's an address of mine
|
|
||||||
|
|
||||||
# add output info for hw wallets
|
# add output info for hw wallets
|
||||||
tx.output_info = []
|
tx.output_info = []
|
||||||
|
@ -1024,12 +1016,9 @@ class Abstract_Wallet(PrintError):
|
||||||
tx.output_info.append((change, address_index))
|
tx.output_info.append((change, address_index))
|
||||||
|
|
||||||
# sign
|
# sign
|
||||||
for keystore in self.get_keystores():
|
for k in self.get_keystores():
|
||||||
if not keystore.is_watching_only():
|
k.sign_transaction(tx, password)
|
||||||
try:
|
|
||||||
keystore.sign_transaction(tx, password)
|
|
||||||
except:
|
|
||||||
print "keystore cannot sign", keystore
|
|
||||||
|
|
||||||
def get_unused_addresses(self):
|
def get_unused_addresses(self):
|
||||||
# fixme: use slots from expired requests
|
# fixme: use slots from expired requests
|
||||||
|
@ -1270,7 +1259,6 @@ class P2PK_Wallet(Abstract_Wallet):
|
||||||
txin['signatures'] = [None]
|
txin['signatures'] = [None]
|
||||||
txin['redeemPubkey'] = pubkey
|
txin['redeemPubkey'] = pubkey
|
||||||
txin['num_sig'] = 1
|
txin['num_sig'] = 1
|
||||||
txin['can_sign'] = any([x is None for x in txin['signatures']])
|
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
sequence = self.get_address_index(address)
|
sequence = self.get_address_index(address)
|
||||||
|
@ -1534,8 +1522,6 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||||
txin['signatures'] = [None] * len(pubkeys)
|
txin['signatures'] = [None] * len(pubkeys)
|
||||||
txin['redeemScript'] = self.redeem_script(*derivation)
|
txin['redeemScript'] = self.redeem_script(*derivation)
|
||||||
txin['num_sig'] = self.m
|
txin['num_sig'] = self.m
|
||||||
txin['can_sign'] = any([x is None for x in txin['signatures']])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
wallet_types = ['standard', 'multisig', 'imported']
|
wallet_types = ['standard', 'multisig', 'imported']
|
||||||
|
|
Loading…
Reference in New Issue