<reponame>ethereum/py-hdwallet<gh_stars>1-10
import abc
import binascii
from typing import (
    Any,
    Optional,
    cast,
)

import base58
from ecdsa.ellipticcurve import (
    INFINITY,
)

from .exceptions import (
    KeyGenerationError,
)
from .typing import (
    ChainCode,
    Fingerprint,
    Identifier,
    Index,
    PrivateKey,
    PublicKey,
)
from .utils import (
    MIN_HARDENED_INDEX,
    PATH_TYPE_ABSOLUTE,
    PATH_TYPE_RELATIVE,
    SECP256k1_ORD,
    curve_point_from_int,
    fingerprint_from_priv_key,
    fingerprint_from_pub_key,
    hmac_sha512,
    identifier_from_priv_key,
    identifier_from_pub_key,
    parse_bip32_path,
    parse_uint256,
    serialize_curve_point,
    serialize_uint32,
    serialize_uint256,
)

BITCOIN_VERSION_BYTES = {
    'mainnet_public': binascii.unhexlify('0488b21e'),
    'mainnet_private': binascii.unhexlify('0488ade4'),
    'testnet_public': binascii.unhexlify('043587cf'),
    'testnet_private': binascii.unhexlify('04358394'),
}


class ExtPrivateKey:
    """
    A class to represent the key/chain-code tuple of an extended private key in
    a wallet hierarchy.  Can represent a master extended private key or any
    child extended private key.
    """
    __slots__ = ('private_key', 'chain_code')

    private_key: PrivateKey
    chain_code: ChainCode

    def __init__(self, private_key: PrivateKey, chain_code: ChainCode) -> None:
        self.private_key = private_key
        """
        The integer value of the ECC private key contained in an extended
        private key.
        """

        self.chain_code = chain_code
        """
        The bytes of an extended private key's chain code.
        """

    @classmethod
    def master_from_hexstr(cls, hexstr: str) -> 'ExtPrivateKey':
        """
        Return an extended master key generated from seed bytes encoded in the
        hex string ``hexstr``.

        :param hexstr: A string containing a hex representation of the seed
            bytes to use for master key generation.

        :return: The extended master key resulting from generation with seed
            bytes encoded in ``hexstr``.
        """
        seed_bytes = binascii.unhexlify(hexstr)

        return cls.master_from_bytes(seed_bytes)

    @classmethod
    def master_from_bytes(cls, bs: bytes) -> 'ExtPrivateKey':
        """
        Return an extended master key generated from seed bytes ``bs``.

        :param bs: The seed bytes to use for master key generation.

        :return: The extended master key resulting from generation with seed bytes
            ``bs``.
        """
        hmac_bytes = hmac_sha512(b'Bitcoin seed', bs)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        private_key = parse_uint256(L)
        chain_code = R

        if private_key >= SECP256k1_ORD or private_key == 0:
            raise KeyGenerationError('Generated master private key is outside acceptable range')

        return cls(private_key, chain_code)

    def child_ext_private_key(self, i: Index) -> 'ExtPrivateKey':
        """
        Return the child extended private key at index ``i`` for a parent
        extended private key instance.

        :param i: The index of the child key to be generated.

        :return: The child extended private key at index ``i`` for a parent
            extended private key instance.
        """
        if i >= MIN_HARDENED_INDEX:
            # Generate a hardened key
            data = b'\x00' + serialize_uint256(self.private_key) + serialize_uint32(i)
        else:
            # Generate a non-hardened key
            data = serialize_curve_point(
                curve_point_from_int(self.private_key),
            ) + serialize_uint32(i)

        hmac_bytes = hmac_sha512(self.chain_code, data)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        L_as_int = parse_uint256(L)
        private_key_i = (L_as_int + self.private_key) % SECP256k1_ORD
        chain_code_i = R

        if L_as_int >= SECP256k1_ORD or private_key_i == 0:
            raise KeyGenerationError('Generated child private key is outside acceptable range')

        return type(self)(private_key_i, chain_code_i)

    @property
    def ext_public_key(self) -> 'ExtPublicKey':
        """
        The associated extended public key for an extended private key.
        """
        return ExtPublicKey(curve_point_from_int(self.private_key), self.chain_code)

    @property
    def identifier(self) -> Identifier:
        """
        The identifier bytes for an extended private key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return identifier_from_priv_key(self.private_key)

    @property
    def fingerprint(self) -> Fingerprint:
        """
        The fingerprint bytes for an extended private key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return fingerprint_from_priv_key(self.private_key)


class ExtPublicKey:
    """
    A class to represent the key/chain-code tuple of an extended public key in
    a wallet hierarchy.  Can represent a master extended public key or any
    child extended public key.
    """
    __slots__ = ('public_key', 'chain_code')

    public_key: PublicKey
    chain_code: ChainCode

    def __init__(self, public_key: PublicKey, chain_code: ChainCode):
        self.public_key = public_key
        """
        The curve point value of the ECC public key contained in an extended
        public key.
        """

        self.chain_code = chain_code
        """
        The bytes of an extended public key's chain code.
        """

    def child_ext_public_key(self, i: Index) -> 'ExtPublicKey':
        """
        Return the child extended public key at index ``i`` for a parent
        extended public key instance.

        :param i: The index of the child key to be generated.

        :return: The child extended public key at index ``i`` for the parent
            extended public key instance.
        """
        if i >= MIN_HARDENED_INDEX:
            # Not possible, fail
            raise KeyGenerationError('Cannot generate hardened key from public key')
        else:
            # Generate a non-hardened key
            data = serialize_curve_point(self.public_key) + serialize_uint32(i)

        hmac_bytes = hmac_sha512(self.chain_code, data)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        L_as_int = parse_uint256(L)
        public_key_i = curve_point_from_int(L_as_int) + self.public_key
        chain_code_i = R

        if L_as_int >= SECP256k1_ORD or public_key_i == INFINITY:
            raise KeyGenerationError('Generated child public key is outside acceptable range')

        return type(self)(public_key_i, chain_code_i)

    @property
    def identifier(self) -> Identifier:
        """
        The identifier bytes for an extended public key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return identifier_from_pub_key(self.public_key)

    @property
    def fingerprint(self) -> Fingerprint:
        """
        The fingerprint bytes for an extended public key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return fingerprint_from_pub_key(self.public_key)


class WalletNode(abc.ABC):
    """
    Base class for classes that represent extended private or public keys
    located somewhere in an HD wallet's hierarchy.
    """
    __slots__ = ('depth', 'parent_fingerprint', 'child_number', 'parent')

    depth: int
    parent_fingerprint: Fingerprint
    child_number: Index
    parent: Optional['WalletNode']

    def __init__(self, *,
                 depth: int,
                 parent_fingerprint: Fingerprint,
                 child_number: Index,
                 parent: 'WalletNode' = None) -> None:
        self.depth = depth
        """
        The zero-indexed depth at which a node's extended key was generated in
        a wallet hierarchy.  For master keys, this value will be zero.  For
        children of master keys, it will be one, etc.
        """

        self.parent_fingerprint = parent_fingerprint
        """
        The four byte fingerprint of the key from which a node's extended key
        was generated.  For master keys, this value will be four zero-bytes.
        """

        self.child_number = child_number
        """
        The zero-indexed child number of a node's extended key.  This
        identifies the branch taken to generate a node's extended key under its
        parent node.  For master keys, this value will be zero.
        """

        self.parent = parent
        """
        If present, the parent wallet node from which a wallet node was
        generated.  For master keys, or keys loaded from a serialized format,
        this value will be ``None``.
        """

    @property
    def is_master(self) -> bool:
        """
        Equal to ``True`` if a wallet node is a master wallet node.  Otherwise,
        ``False``.
        """
        return self.depth == 0

    @abc.abstractproperty
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a wallet node's private or public key.  Used in
        the node's base58 serialization.
        """
        pass

    @abc.abstractproperty
    def chain_code(self) -> bytes:
        """
        The chain code for a wallet node's extended private or public key.
        """
        pass

    def to_base58(self, network: str) -> str:
        """
        Return the base58 serialization of a wallet node.

        :param network: The label of the network for which the serialized key
            is valid.

        :return: A string containing the base58 serialization of a wallet node.
        """
        version_bytes = BITCOIN_VERSION_BYTES[network]
        depth_byte = self.depth.to_bytes(1, 'big')
        child_number_bytes = serialize_uint32(self.child_number)
        chain_code = self.chain_code
        key_bytes = self.serialized_key_bytes

        all_parts = (
            version_bytes,
            depth_byte,
            self.parent_fingerprint,
            child_number_bytes,
            chain_code,
            key_bytes,
        )
        assert tuple(map(len, all_parts)) == (
            4,  # version bytes
            1,  # depth bytes
            4,  # fingerprint bytes
            4,  # child number bytes
            32,  # chain code bytes
            33,  # key bytes
        )

        all_bytes = b''.join(all_parts)

        return cast(str, base58.b58encode_check(all_bytes).decode('utf8'))


class PrivateWalletNode(WalletNode):
    """
    A class that represents an extended private key located somewhere in an HD
    wallet's hierarchy.
    """
    __slots__ = ('ext_private_key',)

    ext_private_key: ExtPrivateKey

    def __init__(self, *, ext_private_key: ExtPrivateKey, **kwargs: Any) -> None:
        self.ext_private_key = ext_private_key

        super().__init__(**kwargs)

    @property
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a private wallet node's private key.  Used in the
        node's base58 serialization.
        """
        return b'\x00' + serialize_uint256(self.ext_private_key.private_key)

    @property
    def chain_code(self) -> bytes:
        """
        The chain code of a private wallet node's extended private key.
        """
        return self.ext_private_key.chain_code

    def child_private_wallet_node(self, i: Index) -> 'PrivateWalletNode':
        """
        Return the child private wallet node at index ``i`` for a parent wallet
        node.

        :param i: The index of the child private wallet node.

        :return: The child private wallet node at index ``i`` for a parent
            wallet node.
        """
        child_ext_private_key = self.ext_private_key.child_ext_private_key(i)
        fingerprint = self.ext_private_key.fingerprint

        return type(self)(
            ext_private_key=child_ext_private_key,
            depth=self.depth + 1,
            parent_fingerprint=fingerprint,
            child_number=i,
            parent=self,
        )

    def child_from_path(self, path: str) -> 'PrivateWalletNode':
        """
        Return the child private wallet node located at the given absolute or
        relative path for a parent private wallet node.  If a parent private
        wallet node is a master private wallet node, then the given path must
        be absolute (it must begin with "m" or "M").  Otherwise, it must be
        relative (must not begin with "m" or "M").

        :param path: The path of the child wallet node.

        :return: The child wallet node located at the given path.
        """
        if self.is_master:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_ABSOLUTE)
        else:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_RELATIVE)

        child_node = self
        for i in child_numbers:
            child_node = child_node.child_private_wallet_node(i)

        return child_node

    @classmethod
    def master_from_ext_private_key(
        cls,
        ext_private_key: ExtPrivateKey,
    ) -> 'PrivateWalletNode':
        """
        Return a master private wallet node created from an extended private
        key.

        :param ext_private_key: The extended private key to use when creating
            the wallet node.

        :return: A master private wallet node.
        """
        return cls(
            ext_private_key=ext_private_key,
            depth=0,
            parent_fingerprint=b'\x00' * 4,
            child_number=0,
        )

    @classmethod
    def master_from_hexstr(cls, seed_hex_str: str) -> 'PrivateWalletNode':
        """
        Return a master private wallet node generated from seed bytes encoded
        in the hex string ``hexstr``.

        :param hexstr: A string containing a hex representation of the seed
            bytes to use for master node generation.

        :return: The master private wallet node resulting from generation with
            seed bytes encoded in ``hexstr``.
        """
        ext_private_key = ExtPrivateKey.master_from_hexstr(seed_hex_str)

        return cls.master_from_ext_private_key(ext_private_key)

    @classmethod
    def master_from_bytes(cls, bs: bytes) -> 'PrivateWalletNode':
        """
        Return a master private wallet node generated from seed bytes ``bs``.

        :param bs: The seed bytes to use for master node generation.

        :return: The master private wallet node resulting from generation with
            seed bytes ``bs``.
        """
        ext_private_key = ExtPrivateKey.master_from_bytes(bs)

        return cls.master_from_ext_private_key(ext_private_key)

    @property
    def public_wallet_node(self) -> 'PublicWalletNode':
        """
        The public wallet node corresponding to a private wallet node.
        """
        return PublicWalletNode(
            ext_public_key=self.ext_private_key.ext_public_key,
            depth=self.depth,
            parent_fingerprint=self.parent_fingerprint,
            child_number=self.child_number,
            parent=self.parent,
        )


class PublicWalletNode(WalletNode):
    """
    A class that represents an extended public key located somewhere in an HD
    wallet's hierarchy.
    """
    __slots__ = ('ext_public_key',)

    ext_public_key: ExtPublicKey

    def __init__(self, *, ext_public_key: ExtPublicKey, **kwargs: Any) -> None:
        self.ext_public_key = ext_public_key

        super().__init__(**kwargs)

    @property
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a public wallet node's public key.  Used in the
        node's base58 serialization.
        """
        return serialize_curve_point(self.ext_public_key.public_key)

    @property
    def chain_code(self) -> bytes:
        """
        The chain code of a public wallet node's extended public key.
        """
        return self.ext_public_key.chain_code

    def child_public_wallet_node(self, i: Index) -> 'PublicWalletNode':
        """
        Return the child public wallet node at index ``i`` for a parent wallet
        node.

        :param i: The index of the child public wallet node.

        :return: The child public wallet node at index ``i`` for a parent
            wallet node.
        """
        child_ext_public_key = self.ext_public_key.child_ext_public_key(i)
        fingerprint = self.ext_public_key.fingerprint

        return type(self)(
            ext_public_key=child_ext_public_key,
            depth=self.depth + 1,
            parent_fingerprint=fingerprint,
            child_number=i,
            parent=self,
        )

    def child_from_path(self, path: str) -> 'PublicWalletNode':
        """
        Return the child public wallet node located at the given absolute or
        relative path for a parent public wallet node.  If a parent public
        wallet node is a master public wallet node, then the given path must be
        absolute (it must begin with "m" or "M").  Otherwise, it must be
        relative (must not begin with "m" or "M").

        :param path: The path of the child wallet node.

        :return: The child wallet node located at the given path.
        """
        if self.is_master:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_ABSOLUTE)
        else:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_RELATIVE)

        child_node = self
        for i in child_numbers:
            child_node = child_node.child_public_wallet_node(i)

        return child_node

import abc
import binascii
from typing import (
    Any,
    Optional,
    cast,
)

import base58
from ecdsa.ellipticcurve import (
    INFINITY,
)

from .exceptions import (
    KeyGenerationError,
)
from .typing import (
    ChainCode,
    Fingerprint,
    Identifier,
    Index,
    PrivateKey,
    PublicKey,
)
from .utils import (
    MIN_HARDENED_INDEX,
    PATH_TYPE_ABSOLUTE,
    PATH_TYPE_RELATIVE,
    SECP256k1_ORD,
    curve_point_from_int,
    fingerprint_from_priv_key,
    fingerprint_from_pub_key,
    hmac_sha512,
    identifier_from_priv_key,
    identifier_from_pub_key,
    parse_bip32_path,
    parse_uint256,
    serialize_curve_point,
    serialize_uint32,
    serialize_uint256,
)

BITCOIN_VERSION_BYTES = {
    'mainnet_public': binascii.unhexlify('0488b21e'),
    'mainnet_private': binascii.unhexlify('0488ade4'),
    'testnet_public': binascii.unhexlify('043587cf'),
    'testnet_private': binascii.unhexlify('04358394'),
}


class ExtPrivateKey:
    """
    A class to represent the key/chain-code tuple of an extended private key in
    a wallet hierarchy.  Can represent a master extended private key or any
    child extended private key.
    """
    __slots__ = ('private_key', 'chain_code')

    private_key: PrivateKey
    chain_code: ChainCode

    def __init__(self, private_key: PrivateKey, chain_code: ChainCode) -> None:
        self.private_key = private_key
        """
        The integer value of the ECC private key contained in an extended
        private key.
        """

        self.chain_code = chain_code
        """
        The bytes of an extended private key's chain code.
        """

    @classmethod
    def master_from_hexstr(cls, hexstr: str) -> 'ExtPrivateKey':
        """
        Return an extended master key generated from seed bytes encoded in the
        hex string ``hexstr``.

        :param hexstr: A string containing a hex representation of the seed
            bytes to use for master key generation.

        :return: The extended master key resulting from generation with seed
            bytes encoded in ``hexstr``.
        """
        seed_bytes = binascii.unhexlify(hexstr)

        return cls.master_from_bytes(seed_bytes)

    @classmethod
    def master_from_bytes(cls, bs: bytes) -> 'ExtPrivateKey':
        """
        Return an extended master key generated from seed bytes ``bs``.

        :param bs: The seed bytes to use for master key generation.

        :return: The extended master key resulting from generation with seed bytes
            ``bs``.
        """
        hmac_bytes = hmac_sha512(b'Bitcoin seed', bs)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        private_key = parse_uint256(L)
        chain_code = R

        if private_key >= SECP256k1_ORD or private_key == 0:
            raise KeyGenerationError('Generated master private key is outside acceptable range')

        return cls(private_key, chain_code)

    def child_ext_private_key(self, i: Index) -> 'ExtPrivateKey':
        """
        Return the child extended private key at index ``i`` for a parent
        extended private key instance.

        :param i: The index of the child key to be generated.

        :return: The child extended private key at index ``i`` for a parent
            extended private key instance.
        """
        if i >= MIN_HARDENED_INDEX:
            # Generate a hardened key
            data = b'\x00' + serialize_uint256(self.private_key) + serialize_uint32(i)
        else:
            # Generate a non-hardened key
            data = serialize_curve_point(
                curve_point_from_int(self.private_key),
            ) + serialize_uint32(i)

        hmac_bytes = hmac_sha512(self.chain_code, data)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        L_as_int = parse_uint256(L)
        private_key_i = (L_as_int + self.private_key) % SECP256k1_ORD
        chain_code_i = R

        if L_as_int >= SECP256k1_ORD or private_key_i == 0:
            raise KeyGenerationError('Generated child private key is outside acceptable range')

        return type(self)(private_key_i, chain_code_i)

    @property
    def ext_public_key(self) -> 'ExtPublicKey':
        """
        The associated extended public key for an extended private key.
        """
        return ExtPublicKey(curve_point_from_int(self.private_key), self.chain_code)

    @property
    def identifier(self) -> Identifier:
        """
        The identifier bytes for an extended private key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return identifier_from_priv_key(self.private_key)

    @property
    def fingerprint(self) -> Fingerprint:
        """
        The fingerprint bytes for an extended private key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return fingerprint_from_priv_key(self.private_key)


class ExtPublicKey:
    """
    A class to represent the key/chain-code tuple of an extended public key in
    a wallet hierarchy.  Can represent a master extended public key or any
    child extended public key.
    """
    __slots__ = ('public_key', 'chain_code')

    public_key: PublicKey
    chain_code: ChainCode

    def __init__(self, public_key: PublicKey, chain_code: ChainCode):
        self.public_key = public_key
        """
        The curve point value of the ECC public key contained in an extended
        public key.
        """

        self.chain_code = chain_code
        """
        The bytes of an extended public key's chain code.
        """

    def child_ext_public_key(self, i: Index) -> 'ExtPublicKey':
        """
        Return the child extended public key at index ``i`` for a parent
        extended public key instance.

        :param i: The index of the child key to be generated.

        :return: The child extended public key at index ``i`` for the parent
            extended public key instance.
        """
        if i >= MIN_HARDENED_INDEX:
            # Not possible, fail
            raise KeyGenerationError('Cannot generate hardened key from public key')
        else:
            # Generate a non-hardened key
            data = serialize_curve_point(self.public_key) + serialize_uint32(i)

        hmac_bytes = hmac_sha512(self.chain_code, data)
        L, R = hmac_bytes[:32], hmac_bytes[32:]

        L_as_int = parse_uint256(L)
        public_key_i = curve_point_from_int(L_as_int) + self.public_key
        chain_code_i = R

        if L_as_int >= SECP256k1_ORD or public_key_i == INFINITY:
            raise KeyGenerationError('Generated child public key is outside acceptable range')

        return type(self)(public_key_i, chain_code_i)

    @property
    def identifier(self) -> Identifier:
        """
        The identifier bytes for an extended public key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return identifier_from_pub_key(self.public_key)

    @property
    def fingerprint(self) -> Fingerprint:
        """
        The fingerprint bytes for an extended public key as described in BIP32
        (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers).
        """
        return fingerprint_from_pub_key(self.public_key)


class WalletNode(abc.ABC):
    """
    Base class for classes that represent extended private or public keys
    located somewhere in an HD wallet's hierarchy.
    """
    __slots__ = ('depth', 'parent_fingerprint', 'child_number', 'parent')

    depth: int
    parent_fingerprint: Fingerprint
    child_number: Index
    parent: Optional['WalletNode']

    def __init__(self, *,
                 depth: int,
                 parent_fingerprint: Fingerprint,
                 child_number: Index,
                 parent: 'WalletNode' = None) -> None:
        self.depth = depth
        """
        The zero-indexed depth at which a node's extended key was generated in
        a wallet hierarchy.  For master keys, this value will be zero.  For
        children of master keys, it will be one, etc.
        """

        self.parent_fingerprint = parent_fingerprint
        """
        The four byte fingerprint of the key from which a node's extended key
        was generated.  For master keys, this value will be four zero-bytes.
        """

        self.child_number = child_number
        """
        The zero-indexed child number of a node's extended key.  This
        identifies the branch taken to generate a node's extended key under its
        parent node.  For master keys, this value will be zero.
        """

        self.parent = parent
        """
        If present, the parent wallet node from which a wallet node was
        generated.  For master keys, or keys loaded from a serialized format,
        this value will be ``None``.
        """

    @property
    def is_master(self) -> bool:
        """
        Equal to ``True`` if a wallet node is a master wallet node.  Otherwise,
        ``False``.
        """
        return self.depth == 0

    @abc.abstractproperty
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a wallet node's private or public key.  Used in
        the node's base58 serialization.
        """
        pass

    @abc.abstractproperty
    def chain_code(self) -> bytes:
        """
        The chain code for a wallet node's extended private or public key.
        """
        pass

    def to_base58(self, network: str) -> str:
        """
        Return the base58 serialization of a wallet node.

        :param network: The label of the network for which the serialized key
            is valid.

        :return: A string containing the base58 serialization of a wallet node.
        """
        version_bytes = BITCOIN_VERSION_BYTES[network]
        depth_byte = self.depth.to_bytes(1, 'big')
        child_number_bytes = serialize_uint32(self.child_number)
        chain_code = self.chain_code
        key_bytes = self.serialized_key_bytes

        all_parts = (
            version_bytes,
            depth_byte,
            self.parent_fingerprint,
            child_number_bytes,
            chain_code,
            key_bytes,
        )
        assert tuple(map(len, all_parts)) == (
            4,  # version bytes
            1,  # depth bytes
            4,  # fingerprint bytes
            4,  # child number bytes
            32,  # chain code bytes
            33,  # key bytes
        )

        all_bytes = b''.join(all_parts)

        return cast(str, base58.b58encode_check(all_bytes).decode('utf8'))


class PrivateWalletNode(WalletNode):
    """
    A class that represents an extended private key located somewhere in an HD
    wallet's hierarchy.
    """
    __slots__ = ('ext_private_key',)

    ext_private_key: ExtPrivateKey

    def __init__(self, *, ext_private_key: ExtPrivateKey, **kwargs: Any) -> None:
        self.ext_private_key = ext_private_key

        super().__init__(**kwargs)

    @property
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a private wallet node's private key.  Used in the
        node's base58 serialization.
        """
        return b'\x00' + serialize_uint256(self.ext_private_key.private_key)

    @property
    def chain_code(self) -> bytes:
        """
        The chain code of a private wallet node's extended private key.
        """
        return self.ext_private_key.chain_code

    def child_private_wallet_node(self, i: Index) -> 'PrivateWalletNode':
        """
        Return the child private wallet node at index ``i`` for a parent wallet
        node.

        :param i: The index of the child private wallet node.

        :return: The child private wallet node at index ``i`` for a parent
            wallet node.
        """
        child_ext_private_key = self.ext_private_key.child_ext_private_key(i)
        fingerprint = self.ext_private_key.fingerprint

        return type(self)(
            ext_private_key=child_ext_private_key,
            depth=self.depth + 1,
            parent_fingerprint=fingerprint,
            child_number=i,
            parent=self,
        )

    def child_from_path(self, path: str) -> 'PrivateWalletNode':
        """
        Return the child private wallet node located at the given absolute or
        relative path for a parent private wallet node.  If a parent private
        wallet node is a master private wallet node, then the given path must
        be absolute (it must begin with "m" or "M").  Otherwise, it must be
        relative (must not begin with "m" or "M").

        :param path: The path of the child wallet node.

        :return: The child wallet node located at the given path.
        """
        if self.is_master:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_ABSOLUTE)
        else:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_RELATIVE)

        child_node = self
        for i in child_numbers:
            child_node = child_node.child_private_wallet_node(i)

        return child_node

    @classmethod
    def master_from_ext_private_key(
        cls,
        ext_private_key: ExtPrivateKey,
    ) -> 'PrivateWalletNode':
        """
        Return a master private wallet node created from an extended private
        key.

        :param ext_private_key: The extended private key to use when creating
            the wallet node.

        :return: A master private wallet node.
        """
        return cls(
            ext_private_key=ext_private_key,
            depth=0,
            parent_fingerprint=b'\x00' * 4,
            child_number=0,
        )

    @classmethod
    def master_from_hexstr(cls, seed_hex_str: str) -> 'PrivateWalletNode':
        """
        Return a master private wallet node generated from seed bytes encoded
        in the hex string ``hexstr``.

        :param hexstr: A string containing a hex representation of the seed
            bytes to use for master node generation.

        :return: The master private wallet node resulting from generation with
            seed bytes encoded in ``hexstr``.
        """
        ext_private_key = ExtPrivateKey.master_from_hexstr(seed_hex_str)

        return cls.master_from_ext_private_key(ext_private_key)

    @classmethod
    def master_from_bytes(cls, bs: bytes) -> 'PrivateWalletNode':
        """
        Return a master private wallet node generated from seed bytes ``bs``.

        :param bs: The seed bytes to use for master node generation.

        :return: The master private wallet node resulting from generation with
            seed bytes ``bs``.
        """
        ext_private_key = ExtPrivateKey.master_from_bytes(bs)

        return cls.master_from_ext_private_key(ext_private_key)

    @property
    def public_wallet_node(self) -> 'PublicWalletNode':
        """
        The public wallet node corresponding to a private wallet node.
        """
        return PublicWalletNode(
            ext_public_key=self.ext_private_key.ext_public_key,
            depth=self.depth,
            parent_fingerprint=self.parent_fingerprint,
            child_number=self.child_number,
            parent=self.parent,
        )


class PublicWalletNode(WalletNode):
    """
    A class that represents an extended public key located somewhere in an HD
    wallet's hierarchy.
    """
    __slots__ = ('ext_public_key',)

    ext_public_key: ExtPublicKey

    def __init__(self, *, ext_public_key: ExtPublicKey, **kwargs: Any) -> None:
        self.ext_public_key = ext_public_key

        super().__init__(**kwargs)

    @property
    def serialized_key_bytes(self) -> bytes:
        """
        Serialized bytes for a public wallet node's public key.  Used in the
        node's base58 serialization.
        """
        return serialize_curve_point(self.ext_public_key.public_key)

    @property
    def chain_code(self) -> bytes:
        """
        The chain code of a public wallet node's extended public key.
        """
        return self.ext_public_key.chain_code

    def child_public_wallet_node(self, i: Index) -> 'PublicWalletNode':
        """
        Return the child public wallet node at index ``i`` for a parent wallet
        node.

        :param i: The index of the child public wallet node.

        :return: The child public wallet node at index ``i`` for a parent
            wallet node.
        """
        child_ext_public_key = self.ext_public_key.child_ext_public_key(i)
        fingerprint = self.ext_public_key.fingerprint

        return type(self)(
            ext_public_key=child_ext_public_key,
            depth=self.depth + 1,
            parent_fingerprint=fingerprint,
            child_number=i,
            parent=self,
        )

    def child_from_path(self, path: str) -> 'PublicWalletNode':
        """
        Return the child public wallet node located at the given absolute or
        relative path for a parent public wallet node.  If a parent public
        wallet node is a master public wallet node, then the given path must be
        absolute (it must begin with "m" or "M").  Otherwise, it must be
        relative (must not begin with "m" or "M").

        :param path: The path of the child wallet node.

        :return: The child wallet node located at the given path.
        """
        if self.is_master:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_ABSOLUTE)
        else:
            child_numbers = parse_bip32_path(path, path_type=PATH_TYPE_RELATIVE)

        child_node = self
        for i in child_numbers:
            child_node = child_node.child_public_wallet_node(i)

        return child_node
A class to represent the key/chain-code tuple of an extended private key in     a wallet hierarchy.  Can represent a master extended private key or any     child extended private key. The integer value of the ECC private key contained in an extended         private key. The bytes of an extended private key's chain code. Return an extended master key generated from seed bytes encoded in the         hex string ``hexstr``.          :param hexstr: A string containing a hex representation of the seed             bytes to use for master key generation.          :return: The extended master key resulting from generation with seed             bytes encoded in ``hexstr``. Return an extended master key generated from seed bytes ``bs``.          :param bs: The seed bytes to use for master key generation.          :return: The extended master key resulting from generation with seed bytes             ``bs``. Return the child extended private key at index ``i`` for a parent         extended private key instance.          :param i: The index of the child key to be generated.          :return: The child extended private key at index ``i`` for a parent             extended private key instance. # Generate a hardened key # Generate a non-hardened key The associated extended public key for an extended private key. The identifier bytes for an extended private key as described in BIP32         (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers). The fingerprint bytes for an extended private key as described in BIP32         (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers). A class to represent the key/chain-code tuple of an extended public key in     a wallet hierarchy.  Can represent a master extended public key or any     child extended public key. The curve point value of the ECC public key contained in an extended         public key. The bytes of an extended public key's chain code. Return the child extended public key at index ``i`` for a parent         extended public key instance.          :param i: The index of the child key to be generated.          :return: The child extended public key at index ``i`` for the parent             extended public key instance. # Not possible, fail # Generate a non-hardened key The identifier bytes for an extended public key as described in BIP32         (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers). The fingerprint bytes for an extended public key as described in BIP32         (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers). Base class for classes that represent extended private or public keys     located somewhere in an HD wallet's hierarchy. The zero-indexed depth at which a node's extended key was generated in         a wallet hierarchy.  For master keys, this value will be zero.  For         children of master keys, it will be one, etc. The four byte fingerprint of the key from which a node's extended key         was generated.  For master keys, this value will be four zero-bytes. The zero-indexed child number of a node's extended key.  This         identifies the branch taken to generate a node's extended key under its         parent node.  For master keys, this value will be zero. If present, the parent wallet node from which a wallet node was         generated.  For master keys, or keys loaded from a serialized format,         this value will be ``None``. Equal to ``True`` if a wallet node is a master wallet node.  Otherwise,         ``False``. Serialized bytes for a wallet node's private or public key.  Used in         the node's base58 serialization. The chain code for a wallet node's extended private or public key. Return the base58 serialization of a wallet node.          :param network: The label of the network for which the serialized key             is valid.          :return: A string containing the base58 serialization of a wallet node. # version bytes # depth bytes # fingerprint bytes # child number bytes # chain code bytes # key bytes A class that represents an extended private key located somewhere in an HD     wallet's hierarchy. Serialized bytes for a private wallet node's private key.  Used in the         node's base58 serialization. The chain code of a private wallet node's extended private key. Return the child private wallet node at index ``i`` for a parent wallet         node.          :param i: The index of the child private wallet node.          :return: The child private wallet node at index ``i`` for a parent             wallet node. Return the child private wallet node located at the given absolute or         relative path for a parent private wallet node.  If a parent private         wallet node is a master private wallet node, then the given path must         be absolute (it must begin with "m" or "M").  Otherwise, it must be         relative (must not begin with "m" or "M").          :param path: The path of the child wallet node.          :return: The child wallet node located at the given path. Return a master private wallet node created from an extended private         key.          :param ext_private_key: The extended private key to use when creating             the wallet node.          :return: A master private wallet node. Return a master private wallet node generated from seed bytes encoded         in the hex string ``hexstr``.          :param hexstr: A string containing a hex representation of the seed             bytes to use for master node generation.          :return: The master private wallet node resulting from generation with             seed bytes encoded in ``hexstr``. Return a master private wallet node generated from seed bytes ``bs``.          :param bs: The seed bytes to use for master node generation.          :return: The master private wallet node resulting from generation with             seed bytes ``bs``. The public wallet node corresponding to a private wallet node. A class that represents an extended public key located somewhere in an HD     wallet's hierarchy. Serialized bytes for a public wallet node's public key.  Used in the         node's base58 serialization. The chain code of a public wallet node's extended public key. Return the child public wallet node at index ``i`` for a parent wallet         node.          :param i: The index of the child public wallet node.          :return: The child public wallet node at index ``i`` for a parent             wallet node. Return the child public wallet node located at the given absolute or         relative path for a parent public wallet node.  If a parent public         wallet node is a master public wallet node, then the given path must be         absolute (it must begin with "m" or "M").  Otherwise, it must be         relative (must not begin with "m" or "M").          :param path: The path of the child wallet node.          :return: The child wallet node located at the given path.