1
0

integrate PSBT support natively. WIP

This commit is contained in:
SomberNight
2019-10-23 17:09:41 +02:00
parent 6d12ebabbb
commit bafe8a2fff
61 changed files with 3405 additions and 3310 deletions

View File

@@ -26,16 +26,15 @@
from unicodedata import normalize
import hashlib
from typing import Tuple, TYPE_CHECKING, Union, Sequence
from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List
from . import bitcoin, ecc, constants, bip32
from .bitcoin import (deserialize_privkey, serialize_privkey,
public_key_to_p2pkh)
from .bitcoin import deserialize_privkey, serialize_privkey
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
is_xpub, is_xprv, BIP32Node)
is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation)
from .ecc import string_to_number, number_to_string
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160)
from .util import (InvalidPassword, WalletFileException,
BitcoinException, bh2u, bfh, inv_dict)
from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed
@@ -43,7 +42,7 @@ from .plugin import run_hook
from .logging import Logger
if TYPE_CHECKING:
from .transaction import Transaction
from .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput
class KeyStore(Logger):
@@ -67,25 +66,19 @@ class KeyStore(Logger):
"""Returns whether the keystore can be encrypted with a password."""
raise NotImplementedError()
def get_tx_derivations(self, tx):
def get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[str, Union[Sequence[int], str]]:
keypairs = {}
for txin in tx.inputs():
num_sig = txin.get('num_sig')
if num_sig is None:
if txin.is_complete():
continue
x_signatures = txin['signatures']
signatures = [sig for sig in x_signatures if sig]
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:
for pubkey in txin.pubkeys:
if pubkey in txin.part_sigs:
# this pubkey already signed
continue
derivation = self.get_pubkey_derivation(x_pubkey)
derivation = self.get_pubkey_derivation(pubkey, txin)
if not derivation:
continue
keypairs[x_pubkey] = derivation
keypairs[pubkey.hex()] = derivation
return keypairs
def can_sign(self, tx):
@@ -108,9 +101,64 @@ class KeyStore(Logger):
def decrypt_message(self, sequence, message, password) -> bytes:
raise NotImplementedError() # implemented by subclasses
def sign_transaction(self, tx: 'Transaction', password) -> None:
def sign_transaction(self, tx: 'PartialTransaction', password) -> None:
raise NotImplementedError() # implemented by subclasses
def get_pubkey_derivation(self, pubkey: bytes,
txinout: Union['PartialTxInput', 'PartialTxOutput'],
*, only_der_suffix=True) \
-> Union[Sequence[int], str, None]:
"""Returns either a derivation int-list if the pubkey can be HD derived from this keystore,
the pubkey itself (hex) if the pubkey belongs to the keystore but not HD derived,
or None if the pubkey is unrelated.
"""
def test_der_suffix_against_pubkey(der_suffix: Sequence[int], pubkey: bytes) -> bool:
if len(der_suffix) != 2:
return False
if pubkey.hex() != self.derive_pubkey(*der_suffix):
return False
return True
if hasattr(self, 'get_root_fingerprint'):
if pubkey not in txinout.bip32_paths:
return None
fp_found, path_found = txinout.bip32_paths[pubkey]
der_suffix = None
full_path = None
# try fp against our root
my_root_fingerprint_hex = self.get_root_fingerprint()
my_der_prefix_str = self.get_derivation_prefix()
ks_der_prefix = convert_bip32_path_to_list_of_uint32(my_der_prefix_str) if my_der_prefix_str else None
if (my_root_fingerprint_hex is not None and ks_der_prefix is not None and
fp_found.hex() == my_root_fingerprint_hex):
if path_found[:len(ks_der_prefix)] == ks_der_prefix:
der_suffix = path_found[len(ks_der_prefix):]
if not test_der_suffix_against_pubkey(der_suffix, pubkey):
der_suffix = None
# try fp against our intermediate fingerprint
if (der_suffix is None and hasattr(self, 'xpub') and
fp_found == BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node()):
der_suffix = path_found
if not test_der_suffix_against_pubkey(der_suffix, pubkey):
der_suffix = None
if der_suffix is None:
return None
if ks_der_prefix is not None:
full_path = ks_der_prefix + list(der_suffix)
return der_suffix if only_der_suffix else full_path
return None
def find_my_pubkey_in_txinout(
self, txinout: Union['PartialTxInput', 'PartialTxOutput'],
*, only_der_suffix: bool = False
) -> Tuple[Optional[bytes], Optional[List[int]]]:
# note: we assume that this cosigner only has one pubkey in this txin/txout
for pubkey in txinout.bip32_paths:
path = self.get_pubkey_derivation(pubkey, txinout, only_der_suffix=only_der_suffix)
if path and not isinstance(path, (str, bytes)):
return pubkey, list(path)
return None, None
class Software_KeyStore(KeyStore):
@@ -210,14 +258,10 @@ class Imported_KeyStore(Software_KeyStore):
raise InvalidPassword()
return privkey, compressed
def get_pubkey_derivation(self, x_pubkey):
if x_pubkey[0:2] in ['02', '03', '04']:
if x_pubkey in self.keypairs.keys():
return x_pubkey
elif x_pubkey[0:2] == 'fd':
addr = bitcoin.script_to_address(x_pubkey[2:])
if addr in self.addresses:
return self.addresses[addr].get('pubkey')
def get_pubkey_derivation(self, pubkey, txin, *, only_der_suffix=True):
if pubkey.hex() in self.keypairs:
return pubkey.hex()
return None
def update_password(self, old_password, new_password):
self.check_password(old_password)
@@ -230,7 +274,6 @@ class Imported_KeyStore(Software_KeyStore):
self.pw_hash_version = PW_HASH_VERSION_LATEST
class Deterministic_KeyStore(Software_KeyStore):
def __init__(self, d):
@@ -277,15 +320,54 @@ class Deterministic_KeyStore(Software_KeyStore):
class Xpub:
def __init__(self):
def __init__(self, *, derivation_prefix: str = None, root_fingerprint: str = None):
self.xpub = None
self.xpub_receive = None
self.xpub_change = None
# if these are None now, then it is the responsibility of the caller to
# also call self.add_derivation_prefix_and_root_fingerprint:
self._derivation_prefix = derivation_prefix # note: subclass should persist this
self._root_fingerprint = root_fingerprint # note: subclass should persist this
def get_master_public_key(self):
return self.xpub
def derive_pubkey(self, for_change, n):
def get_derivation_prefix(self) -> str:
"""Returns to bip32 path from some root node to self.xpub"""
assert self._derivation_prefix is not None, 'derivation_prefix should have been set already'
return self._derivation_prefix
def get_root_fingerprint(self) -> str:
"""Returns the bip32 fingerprint of the top level node.
This top level node is the node at the beginning of the derivation prefix,
i.e. applying the derivation prefix to it will result self.xpub
"""
assert self._root_fingerprint is not None, 'root_fingerprint should have been set already'
return self._root_fingerprint
def add_derivation_prefix_and_root_fingerprint(self, *, derivation_prefix: str, root_node: BIP32Node):
assert self.xpub
derivation_prefix = normalize_bip32_derivation(derivation_prefix)
# try to derive ourselves from what we were given
child_node1 = root_node.subkey_at_private_derivation(derivation_prefix)
child_pubkey_bytes1 = child_node1.eckey.get_public_key_bytes(compressed=True)
child_node2 = BIP32Node.from_xkey(self.xpub)
child_pubkey_bytes2 = child_node2.eckey.get_public_key_bytes(compressed=True)
if child_pubkey_bytes1 != child_pubkey_bytes2:
raise Exception("(xpub, derivation_prefix, root_node) inconsistency")
# store
self._root_fingerprint = root_node.calc_fingerprint_of_this_node().hex().lower()
self._derivation_prefix = derivation_prefix
def reset_derivation_prefix(self):
assert self.xpub
self._derivation_prefix = 'm'
self._root_fingerprint = BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node().hex().lower()
def derive_pubkey(self, for_change, n) -> str:
for_change = int(for_change)
assert for_change in (0, 1)
xpub = self.xpub_change if for_change else self.xpub_receive
if xpub is None:
rootnode = BIP32Node.from_xkey(self.xpub)
@@ -301,54 +383,13 @@ class Xpub:
node = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(sequence)
return node.eckey.get_public_key_hex(compressed=True)
def get_xpubkey(self, c, i):
def encode_path_int(path_int) -> str:
if path_int < 0xffff:
hex = bitcoin.int_to_hex(path_int, 2)
else:
hex = 'ffff' + bitcoin.int_to_hex(path_int, 4)
return hex
s = ''.join(map(encode_path_int, (c, i)))
return 'ff' + bh2u(bitcoin.DecodeBase58Check(self.xpub)) + s
@classmethod
def parse_xpubkey(self, pubkey):
# type + xpub + derivation
assert pubkey[0:2] == 'ff'
pk = bfh(pubkey)
# xpub:
pk = pk[1:]
xkey = bitcoin.EncodeBase58Check(pk[0:78])
# derivation:
dd = pk[78:]
s = []
while dd:
# 2 bytes for derivation path index
n = int.from_bytes(dd[0:2], byteorder="little")
dd = dd[2:]
# in case of overflow, drop these 2 bytes; and use next 4 bytes instead
if n == 0xffff:
n = int.from_bytes(dd[0:4], byteorder="little")
dd = dd[4:]
s.append(n)
assert len(s) == 2
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):
type = 'bip32'
def __init__(self, d):
Xpub.__init__(self)
Xpub.__init__(self, derivation_prefix=d.get('derivation'), root_fingerprint=d.get('root_fingerprint'))
Deterministic_KeyStore.__init__(self, d)
self.xpub = d.get('xpub')
self.xprv = d.get('xprv')
@@ -360,6 +401,8 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
d = Deterministic_KeyStore.dump(self)
d['xpub'] = self.xpub
d['xprv'] = self.xprv
d['derivation'] = self.get_derivation_prefix()
d['root_fingerprint'] = self.get_root_fingerprint()
return d
def get_master_private_key(self, password):
@@ -388,14 +431,20 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
def is_watching_only(self):
return self.xprv is None
def add_xprv(self, xprv):
def add_xpub(self, xpub, *, default_der_prefix=True):
self.xpub = xpub
if default_der_prefix:
self.reset_derivation_prefix()
def add_xprv(self, xprv, *, default_der_prefix=True):
self.xprv = xprv
self.xpub = bip32.xpub_from_xprv(xprv)
self.add_xpub(bip32.xpub_from_xprv(xprv), default_der_prefix=default_der_prefix)
def add_xprv_from_seed(self, bip32_seed, xtype, derivation):
rootnode = BIP32Node.from_rootseed(bip32_seed, xtype=xtype)
node = rootnode.subkey_at_private_derivation(derivation)
self.add_xprv(node.to_xprv())
self.add_xprv(node.to_xprv(), default_der_prefix=False)
self.add_derivation_prefix_and_root_fingerprint(derivation_prefix=derivation, root_node=rootnode)
def get_private_key(self, sequence, password):
xprv = self.get_master_private_key(password)
@@ -415,6 +464,7 @@ class Old_KeyStore(Deterministic_KeyStore):
def __init__(self, d):
Deterministic_KeyStore.__init__(self, d)
self.mpk = d.get('mpk')
self._root_fingerprint = None
def get_hex_seed(self, password):
return pw_decode(self.seed, password, version=self.pw_hash_version).encode('utf8')
@@ -477,7 +527,7 @@ class Old_KeyStore(Deterministic_KeyStore):
public_key = master_public_key + z*ecc.generator()
return public_key.get_public_key_hex(compressed=False)
def derive_pubkey(self, for_change, n):
def derive_pubkey(self, for_change, n) -> str:
return self.get_pubkey_from_mpk(self.mpk, for_change, n)
def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
@@ -508,31 +558,15 @@ class Old_KeyStore(Deterministic_KeyStore):
def get_master_public_key(self):
return self.mpk
def get_xpubkey(self, for_change, n):
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
return 'fe' + self.mpk + s
def get_derivation_prefix(self) -> str:
return 'm'
@classmethod
def parse_xpubkey(self, x_pubkey):
assert x_pubkey[0:2] == 'fe'
pk = x_pubkey[2:]
mpk = pk[0:128]
dd = pk[128:]
s = []
while dd:
n = int(bitcoin.rev_hex(dd[0:4]), 16)
dd = dd[4:]
s.append(n)
assert len(s) == 2
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 get_root_fingerprint(self) -> str:
if self._root_fingerprint is None:
master_public_key = ecc.ECPubkey(bfh('04'+self.mpk))
xfp = hash_160(master_public_key.get_public_key_bytes(compressed=True))[0:4]
self._root_fingerprint = xfp.hex().lower()
return self._root_fingerprint
def update_password(self, old_password, new_password):
self.check_password(old_password)
@@ -554,14 +588,13 @@ class Hardware_KeyStore(KeyStore, Xpub):
type = 'hardware'
def __init__(self, d):
Xpub.__init__(self)
Xpub.__init__(self, derivation_prefix=d.get('derivation'), root_fingerprint=d.get('root_fingerprint'))
KeyStore.__init__(self)
# Errors and other user interaction is done through the wallet's
# handler. The handler is per-window and preserved across
# device reconnects
self.xpub = d.get('xpub')
self.label = d.get('label')
self.derivation = d.get('derivation')
self.handler = None
run_hook('init_keystore', self)
@@ -582,7 +615,8 @@ class Hardware_KeyStore(KeyStore, Xpub):
'type': self.type,
'hw_type': self.hw_type,
'xpub': self.xpub,
'derivation':self.derivation,
'derivation': self.get_derivation_prefix(),
'root_fingerprint': self.get_root_fingerprint(),
'label':self.label,
}
@@ -704,40 +738,6 @@ def xtype_from_derivation(derivation: str) -> str:
return 'standard'
# extended pubkeys
def is_xpubkey(x_pubkey):
return x_pubkey[0:2] == 'ff'
def parse_xpubkey(x_pubkey):
assert x_pubkey[0:2] == 'ff'
return BIP32_KeyStore.parse_xpubkey(x_pubkey)
def xpubkey_to_address(x_pubkey):
if x_pubkey[0:2] == 'fd':
address = bitcoin.script_to_address(x_pubkey[2:])
return x_pubkey, address
if x_pubkey[0:2] in ['02', '03', '04']:
pubkey = x_pubkey
elif x_pubkey[0:2] == 'ff':
xpub, s = BIP32_KeyStore.parse_xpubkey(x_pubkey)
pubkey = BIP32_KeyStore.get_pubkey_from_xpub(xpub, s)
elif x_pubkey[0:2] == 'fe':
mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
else:
raise BitcoinException("Cannot parse pubkey. prefix: {}"
.format(x_pubkey[0:2]))
if pubkey:
address = public_key_to_p2pkh(bfh(pubkey))
return pubkey, address
def xpubkey_to_pubkey(x_pubkey):
pubkey, address = xpubkey_to_address(x_pubkey)
return pubkey
hw_keystores = {}
def register_keystore(hw_type, constructor):
@@ -861,14 +861,12 @@ def from_old_mpk(mpk):
def from_xpub(xpub):
k = BIP32_KeyStore({})
k.xpub = xpub
k.add_xpub(xpub)
return k
def from_xprv(xprv):
xpub = bip32.xpub_from_xprv(xprv)
k = BIP32_KeyStore({})
k.xprv = xprv
k.xpub = xpub
k.add_xprv(xprv)
return k
def from_master_key(text):