Пример #1
0
    def derive_path(self, path: str) -> 'HDKey':
        """
        Derives a descendant of the current node
        Throws an error if the requested path is not known to be a descendant

        Args:
            path (str): the requested derivation path from master
        Returns:
            (HDKey): the descendant
        """
        if not self.path:
            raise XPUBError('XPUB current key\'s path is unknown')

        own_path = self.path
        path_nodes = self._parse_derivation(path)
        my_nodes = self._parse_derivation(own_path)

        # compare own path to requested path see if it is a descendant
        for i, my_node in enumerate(my_nodes):
            if my_node != path_nodes[i]:
                raise XPUBError(
                    'XPUB requested child not in descendant branches')

        # iteratively derive descendants through the path
        current_node = self
        for i in range(len(my_nodes), len(path_nodes)):
            current_node = current_node.derive_child(path_nodes[i])
        return current_node
Пример #2
0
    def from_xpub(
        xpub: str,
        xpub_type: Optional[XpubType] = None,
        path: Optional[str] = None,
    ) -> 'HDKey':
        """
        Instantiate an HDKey from an xpub. Populates all possible fields
        Args:
            xpub (str): the xpub
            path (str): the path if it's known. useful for calling derive_path
        Returns:
            (HDKey): the key object

        May raise:
        - XPUBError if there is a problem with decoding the xpub
        """
        xpub_bytes = b58decode(xpub)
        if len(xpub_bytes) < 78:
            raise XPUBError(f'Given XPUB {xpub} is too small')

        try:
            pubkey = PublicKey(xpub_bytes[45:78])
        except ValueError as e:
            raise XPUBError(str(e)) from e

        result = _parse_prefix(xpub_bytes[0:4])
        if not result.is_public:
            raise XPUBError('Given xpub is an extended private key')

        if result.network != 'mainnet':
            raise XPUBError('Given xpub is not for the bitcoin mainnet')

        hint = result.hint
        if xpub_type is not None and xpub_type.matches_prefix(
                xpub[0:4]) is False:
            # the given type does not match the prefix, re-encode with correct pref
            new_xpub = bytearray()
            new_xpub.extend(xpub_type.prefix_bytes())
            new_xpub.extend(xpub_bytes[4:])
            new_xpub_bytes = new_xpub
            hint = xpub_type.prefix()
            xpub = b58encode(bytes(new_xpub_bytes)).decode('ascii')

        return HDKey(
            path=path,
            network=result.network,
            depth=xpub_bytes[4],
            parent_fingerprint=xpub_bytes[5:9],
            index=int.from_bytes(xpub_bytes[9:13], byteorder='big'),
            parent=None,
            chain_code=xpub_bytes[13:45],
            fingerprint=hash160(pubkey.format(COMPRESSED_PUBKEY))[:4],
            xpriv=None,
            xpub=xpub,
            privkey=None,
            pubkey=pubkey,
            hint=hint,
        )
Пример #3
0
    def _parse_derivation(derivation_path: str) -> List[int]:
        """
        turns a derivation path (e.g. m/44h/0) into a list of integer indexes
            e.g. [2147483692, 0]
        Args:
            derivation_path (str): the human-readable derivation path
        Returns:
            (list(int)): the derivaion path as a list of indexes
        """
        int_nodes: List[int] = []

        # Must be / separated
        nodes: List[str] = derivation_path.split('/')
        # If the first node is not m, error.
        # TODO: allow partial path knowledge
        if nodes[0] != 'm':
            raise XPUBError(f'Got bad xpub path {derivation_path} for xpub')

        # Go over all other nodes, and convert to indexes
        nodes = nodes[1:]
        for node in nodes:
            if node[-1] in ['h', "'"]:  # Support 0h and 0' conventions
                int_nodes.append(int(node[:-1]) + BIP32_HARDEN)
            else:
                int_nodes.append(int(node))
        return int_nodes
Пример #4
0
    def from_xpub(xpub: str, path: Optional[str] = None) -> 'HDKey':
        """
        Instantiate an HDKey from an xpub. Populates all possible fields
        Args:
            xpub (str): the xpub
            path (str): the path if it's known. useful for calling derive_path
        Returns:
            (HDKey): the key object

        May raise:
        - XPUBError if there is a problem with decoding the xpub
        """
        xpub_bytes = b58decode(xpub)
        if len(xpub_bytes) < 78:
            raise XPUBError(f'Given XPUB {xpub} is too small')

        try:
            pubkey = PublicKey(xpub_bytes[45:78])
        except ValueError as e:
            raise XPUBError(str(e)) from e

        result = _parse_prefix(xpub_bytes[0:4])
        if not result.is_public:
            raise XPUBError('Given xpub is an extended private key')

        return HDKey(
            path=path,
            network=result.network,
            depth=xpub_bytes[4],
            parent_fingerprint=xpub_bytes[5:9],
            index=int.from_bytes(xpub_bytes[9:13], byteorder='big'),
            parent=None,
            chain_code=xpub_bytes[13:45],
            fingerprint=hash160(pubkey.format(COMPRESSED_PUBKEY))[:4],
            xpriv=None,
            xpub=xpub,
            privkey=None,
            pubkey=pubkey,
            hint=result.hint,
        )
Пример #5
0
 def _normalize_index(idx: Union[int, str]) -> int:
     """
     Normalizes an index so that we can accept ints or strings
     Args:
         idx (int or str): the index as an integer, or a string with h/'
     Returns:
         (int): the index as an integer
     """
     if isinstance(idx, int):
         return idx
     if not isinstance(idx, str):
         raise XPUBError('XPUB path index must be string or integer')
     if idx[-1] in ['h', "'"]:  # account for h or ' conventions
         return int(idx[:-1]) + BIP32_HARDEN
     return int(idx)
Пример #6
0
    def _child_from_xpub(self, index: int, child_xpub: str) -> 'HDKey':
        """
        Returns a new HDKey object based on the current object and the new
            child xpub. Don't call this directly, it's for child derivation.
        Args:
            index      (int): the index of the child
            child_xpub (str): the child's xpub
        Returns
            HDKey: the new child object
        """
        path: Optional[str]
        if self.path is not None:
            path = '{}/{}'.format(self.path, str(index))
        else:
            path = None
        xpub_bytes = b58decode(child_xpub)
        pubkey = xpub_bytes[45:78]

        result = _parse_prefix(xpub_bytes[0:4])
        if not result.is_public:
            raise XPUBError('Given xpub is an extended private key')

        return HDKey(
            path=path,
            network=result.network,
            depth=xpub_bytes[4],
            parent_fingerprint=xpub_bytes[5:9],
            index=int.from_bytes(xpub_bytes[9:13], byteorder='big'),
            parent=self,
            chain_code=xpub_bytes[13:45],
            fingerprint=hash160(pubkey)[:4],
            xpriv=None,
            xpub=child_xpub,
            privkey=None,
            pubkey=PublicKey(pubkey),
            hint=result.hint,
        )
Пример #7
0
    def derive_child(self, idx: Union[int, str]) -> 'HDKey':
        """
        Derives a bip32 child node from the current node
        Args:
            idx (int or str): the index of the child
        Returns:
            (HDKey): the child
        """
        # TODO: Break up this function

        # normalize the index, error if we can't derive the child
        index: int = self._normalize_index(idx)
        if index >= BIP32_HARDEN and not self.privkey:
            raise XPUBError(
                'Need private key to derive XPUB hardened children')

        # error if we can't derive a child
        if not self.chain_code:
            raise XPUBError('Cannot derive XPUB child without chain_code')

        own_chain_code = self.chain_code
        # start key derivation process
        data = bytearray()
        index_as_bytes = index.to_bytes(4, byteorder='big')
        if index >= BIP32_HARDEN:
            if not self.privkey:
                raise XPUBError(
                    'Cannot derive XPUB for hardened index without privkey')
            # Data = 0x00 || ser256(kpar) || ser32(i)
            # (Note: The 0x00 pads the private key to make it 33 bytes long.)
            data.extend(b'\x00')
            data.extend(cast(bytes, self.privkey.secret))
            data.extend(index_as_bytes)
        else:
            # Data = serP(point(kpar)) || ser32(i)).
            data.extend(self.pubkey.format(COMPRESSED_PUBKEY))
            data.extend(index_as_bytes)

        mac = hmac.new(own_chain_code, digestmod=hashlib.sha512)
        mac.update(data)
        digest = mac.digest()  # noqa: E741
        tweak, chain_code = digest[:32], digest[32:]
        # end key derivation process

        try:
            if self.privkey:
                raise NotImplementedError(
                    'Privkeys xpub derivation not implemented in Rotki')
                # if we have a private key, give the child a private key
                # child_privkey = self.privkey.add(tweak)
                # child_pubkey = PublicKey.from_secret(child_privkey.secret)

            # otherwise, just derive a pubkey
            child_pubkey = self.pubkey.add(tweak)
        except ValueError:
            # NB: it is possible to derive an "impossible" key.
            #     e.g. the privkey is too high, or is 0
            #     if that happens, the spec says to derive at the next index
            return self.derive_child(index + 1)

        # no privkey here so make a new public child
        child_xpub = self._make_child_xpub(
            child_pubkey,
            index=index,
            chain_code=chain_code,
        )
        return self._child_from_xpub(index=index, child_xpub=child_xpub)
Пример #8
0
def _parse_prefix(prefix: bytes) -> PrefixParsingResult:
    result = VERSION_BYTES.get(prefix, None)
    if not result:
        raise XPUBError(f'Unknown XPUB prefix {prefix.hex()} found')

    return result