psbt: put fake xpubs into globals. keystores handle xfp/der_prefix missing
This commit is contained in:
@@ -26,12 +26,14 @@
|
||||
|
||||
from unicodedata import normalize
|
||||
import hashlib
|
||||
from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List
|
||||
import re
|
||||
from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List, NamedTuple
|
||||
|
||||
from . import bitcoin, ecc, constants, bip32
|
||||
from .bitcoin import deserialize_privkey, serialize_privkey
|
||||
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
|
||||
is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation)
|
||||
is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation,
|
||||
convert_bip32_intpath_to_strpath)
|
||||
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, hash_160)
|
||||
@@ -49,6 +51,7 @@ class KeyStore(Logger):
|
||||
|
||||
def __init__(self):
|
||||
Logger.__init__(self)
|
||||
self.is_requesting_to_be_rewritten_to_wallet_file = False # type: bool
|
||||
|
||||
def has_seed(self):
|
||||
return False
|
||||
@@ -325,30 +328,60 @@ class Xpub:
|
||||
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
|
||||
# "key origin" info (subclass should persist these):
|
||||
self._derivation_prefix = derivation_prefix # type: Optional[str]
|
||||
self._root_fingerprint = root_fingerprint # type: Optional[str]
|
||||
|
||||
def get_master_public_key(self):
|
||||
return self.xpub
|
||||
|
||||
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'
|
||||
def get_derivation_prefix(self) -> Optional[str]:
|
||||
"""Returns to bip32 path from some root node to self.xpub
|
||||
Note that the return value might be None; if it is unknown.
|
||||
"""
|
||||
return self._derivation_prefix
|
||||
|
||||
def get_root_fingerprint(self) -> str:
|
||||
def get_root_fingerprint(self) -> Optional[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
|
||||
Note that the return value might be None; if it is unknown.
|
||||
"""
|
||||
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):
|
||||
def get_fp_and_derivation_to_be_used_in_partial_tx(self, der_suffix: Sequence[int]) -> Tuple[bytes, Sequence[int]]:
|
||||
"""Returns fingerprint and 'full' derivation path corresponding to a derivation suffix.
|
||||
The fingerprint is either the root fp or the intermediate fp, depending on what is available,
|
||||
and the 'full' derivation path is adjusted accordingly.
|
||||
"""
|
||||
fingerprint_hex = self.get_root_fingerprint()
|
||||
der_prefix_str = self.get_derivation_prefix()
|
||||
if fingerprint_hex is not None and der_prefix_str is not None:
|
||||
# use root fp, and true full path
|
||||
fingerprint_bytes = bfh(fingerprint_hex)
|
||||
der_prefix_ints = convert_bip32_path_to_list_of_uint32(der_prefix_str)
|
||||
else:
|
||||
# use intermediate fp, and claim der suffix is the full path
|
||||
fingerprint_bytes = BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node()
|
||||
der_prefix_ints = convert_bip32_path_to_list_of_uint32('m')
|
||||
der_full = der_prefix_ints + list(der_suffix)
|
||||
return fingerprint_bytes, der_full
|
||||
|
||||
def get_xpub_to_be_used_in_partial_tx(self) -> str:
|
||||
assert self.xpub
|
||||
fp_bytes, der_full = self.get_fp_and_derivation_to_be_used_in_partial_tx(der_suffix=[])
|
||||
bip32node = BIP32Node.from_xkey(self.xpub)
|
||||
depth = len(der_full)
|
||||
child_number_int = der_full[-1] if len(der_full) >= 1 else 0
|
||||
child_number_bytes = child_number_int.to_bytes(length=4, byteorder="big")
|
||||
fingerprint = bytes(4) if depth == 0 else bip32node.fingerprint
|
||||
bip32node = bip32node._replace(depth=depth,
|
||||
fingerprint=fingerprint,
|
||||
child_number=child_number_bytes)
|
||||
return bip32node.to_xpub()
|
||||
|
||||
def add_key_origin_from_root_node(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)
|
||||
@@ -356,14 +389,13 @@ class 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
|
||||
self.add_key_origin(derivation_prefix=derivation_prefix,
|
||||
root_fingerprint=root_node.calc_fingerprint_of_this_node().hex().lower())
|
||||
|
||||
def reset_derivation_prefix(self):
|
||||
def add_key_origin(self, *, derivation_prefix: Optional[str], root_fingerprint: Optional[str]):
|
||||
assert self.xpub
|
||||
self._derivation_prefix = 'm'
|
||||
self._root_fingerprint = BIP32Node.from_xkey(self.xpub).calc_fingerprint_of_this_node().hex().lower()
|
||||
self._root_fingerprint = root_fingerprint
|
||||
self._derivation_prefix = normalize_bip32_derivation(derivation_prefix)
|
||||
|
||||
def derive_pubkey(self, for_change, n) -> str:
|
||||
for_change = int(for_change)
|
||||
@@ -431,20 +463,22 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||
def is_watching_only(self):
|
||||
return self.xprv is None
|
||||
|
||||
def add_xpub(self, xpub, *, default_der_prefix=True):
|
||||
def add_xpub(self, xpub):
|
||||
assert is_xpub(xpub)
|
||||
self.xpub = xpub
|
||||
if default_der_prefix:
|
||||
self.reset_derivation_prefix()
|
||||
root_fingerprint, derivation_prefix = bip32.root_fp_and_der_prefix_from_xkey(xpub)
|
||||
self.add_key_origin(derivation_prefix=derivation_prefix, root_fingerprint=root_fingerprint)
|
||||
|
||||
def add_xprv(self, xprv, *, default_der_prefix=True):
|
||||
def add_xprv(self, xprv):
|
||||
assert is_xprv(xprv)
|
||||
self.xprv = xprv
|
||||
self.add_xpub(bip32.xpub_from_xprv(xprv), default_der_prefix=default_der_prefix)
|
||||
self.add_xpub(bip32.xpub_from_xprv(xprv))
|
||||
|
||||
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(), default_der_prefix=False)
|
||||
self.add_derivation_prefix_and_root_fingerprint(derivation_prefix=derivation, root_node=rootnode)
|
||||
self.add_xprv(node.to_xprv())
|
||||
self.add_key_origin_from_root_node(derivation_prefix=derivation, root_node=rootnode)
|
||||
|
||||
def get_private_key(self, sequence, password):
|
||||
xprv = self.get_master_private_key(password)
|
||||
@@ -568,6 +602,15 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||
self._root_fingerprint = xfp.hex().lower()
|
||||
return self._root_fingerprint
|
||||
|
||||
# TODO Old_KeyStore and Xpub could share a common baseclass?
|
||||
def get_fp_and_derivation_to_be_used_in_partial_tx(self, der_suffix: Sequence[int]) -> Tuple[bytes, Sequence[int]]:
|
||||
fingerprint_hex = self.get_root_fingerprint()
|
||||
der_prefix_str = self.get_derivation_prefix()
|
||||
fingerprint_bytes = bfh(fingerprint_hex)
|
||||
der_prefix_ints = convert_bip32_path_to_list_of_uint32(der_prefix_str)
|
||||
der_full = der_prefix_ints + list(der_suffix)
|
||||
return fingerprint_bytes, der_full
|
||||
|
||||
def update_password(self, old_password, new_password):
|
||||
self.check_password(old_password)
|
||||
if new_password == '':
|
||||
@@ -658,6 +701,14 @@ class Hardware_KeyStore(KeyStore, Xpub):
|
||||
def ready_to_sign(self):
|
||||
return super().ready_to_sign() and self.has_usable_connection_with_device()
|
||||
|
||||
def opportunistically_fill_in_missing_info_from_device(self, client):
|
||||
assert client is not None
|
||||
if self._root_fingerprint is None:
|
||||
root_xpub = client.get_xpub('m', xtype='standard')
|
||||
root_fingerprint = BIP32Node.from_xkey(root_xpub).calc_fingerprint_of_this_node().hex().lower()
|
||||
self._root_fingerprint = root_fingerprint
|
||||
self.is_requesting_to_be_rewritten_to_wallet_file = True
|
||||
|
||||
|
||||
def bip39_normalize_passphrase(passphrase):
|
||||
return normalize('NFKD', passphrase or '')
|
||||
@@ -718,16 +769,17 @@ PURPOSE48_SCRIPT_TYPES_INV = inv_dict(PURPOSE48_SCRIPT_TYPES)
|
||||
|
||||
def xtype_from_derivation(derivation: str) -> str:
|
||||
"""Returns the script type to be used for this derivation."""
|
||||
if derivation.startswith("m/84'"):
|
||||
return 'p2wpkh'
|
||||
elif derivation.startswith("m/49'"):
|
||||
return 'p2wpkh-p2sh'
|
||||
elif derivation.startswith("m/44'"):
|
||||
return 'standard'
|
||||
elif derivation.startswith("m/45'"):
|
||||
return 'standard'
|
||||
|
||||
bip32_indices = convert_bip32_path_to_list_of_uint32(derivation)
|
||||
if len(bip32_indices) >= 1:
|
||||
if bip32_indices[0] == 84 + BIP32_PRIME:
|
||||
return 'p2wpkh'
|
||||
elif bip32_indices[0] == 49 + BIP32_PRIME:
|
||||
return 'p2wpkh-p2sh'
|
||||
elif bip32_indices[0] == 44 + BIP32_PRIME:
|
||||
return 'standard'
|
||||
elif bip32_indices[0] == 45 + BIP32_PRIME:
|
||||
return 'standard'
|
||||
|
||||
if len(bip32_indices) >= 4:
|
||||
if bip32_indices[0] == 48 + BIP32_PRIME:
|
||||
# m / purpose' / coin_type' / account' / script_type' / change / address_index
|
||||
@@ -770,7 +822,7 @@ def load_keystore(storage, name) -> KeyStore:
|
||||
|
||||
def is_old_mpk(mpk: str) -> bool:
|
||||
try:
|
||||
int(mpk, 16)
|
||||
int(mpk, 16) # test if hex string
|
||||
except:
|
||||
return False
|
||||
if len(mpk) != 128:
|
||||
@@ -804,16 +856,18 @@ def is_private_key_list(text, *, allow_spaces_inside_key=True, raise_on_error=Fa
|
||||
raise_on_error=raise_on_error))
|
||||
|
||||
|
||||
is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
|
||||
is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
|
||||
is_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x)
|
||||
is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
|
||||
is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
|
||||
def is_master_key(x):
|
||||
return is_old_mpk(x) or is_bip32_key(x)
|
||||
|
||||
|
||||
def is_bip32_key(x):
|
||||
return is_xprv(x) or is_xpub(x)
|
||||
|
||||
|
||||
def bip44_derivation(account_id, bip43_purpose=44):
|
||||
coin = constants.net.BIP44_COIN_TYPE
|
||||
return "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id))
|
||||
der = "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id))
|
||||
return normalize_bip32_derivation(der)
|
||||
|
||||
|
||||
def purpose48_derivation(account_id: int, xtype: str) -> str:
|
||||
@@ -824,7 +878,8 @@ def purpose48_derivation(account_id: int, xtype: str) -> str:
|
||||
script_type_int = PURPOSE48_SCRIPT_TYPES.get(xtype)
|
||||
if script_type_int is None:
|
||||
raise Exception('unknown xtype: {}'.format(xtype))
|
||||
return "m/%d'/%d'/%d'/%d'" % (bip43_purpose, coin, account_id, script_type_int)
|
||||
der = "m/%d'/%d'/%d'/%d'" % (bip43_purpose, coin, account_id, script_type_int)
|
||||
return normalize_bip32_derivation(der)
|
||||
|
||||
|
||||
def from_seed(seed, passphrase, is_p2sh=False):
|
||||
|
||||
Reference in New Issue
Block a user