import os
from typing import Any, Union
import nacl.signing as ed25519
from nacl.exceptions import BadSignatureError as NaclBadSignatureError
from .sep.mnemonic import Language, StellarMnemonic
from .exceptions import (
BadSignatureError,
MissingEd25519SecretSeedError,
TypeError,
AttributeError,
)
from .strkey import StrKey
from .xdr import Xdr
__all__ = ["Keypair"]
[docs]class Keypair:
"""The :class:`Keypair` object, which represents a signing and
verifying key for use with the Stellar network.
Instead of instantiating the class directly, we recommend using one of
several class methods:
* :meth:`Keypair.random`
* :meth:`Keypair.from_secret`
* :meth:`Keypair.from_public_key`
:param verify_key: The verifying (public) Ed25519 key in the keypair.
:param signing_key: The signing (private) Ed25519 key in the keypair.
"""
def __init__(
self, verify_key: ed25519.VerifyKey, signing_key: ed25519.SigningKey = None
) -> None:
self.verify_key: ed25519.VerifyKey = _get_key_of_expected_type(
verify_key, ed25519.VerifyKey
)
self.signing_key: ed25519.SigningKey = _get_key_of_expected_type(
signing_key, ed25519.SigningKey
)
[docs] @classmethod
def random(cls) -> "Keypair":
"""Generate a :class:`Keypair` object from a randomly generated seed.
:return: A new :class:`Keypair` instance derived by the randomly seed.
"""
seed = os.urandom(32)
return cls.from_raw_ed25519_seed(seed)
[docs] @classmethod
def from_secret(cls, secret: str) -> "Keypair":
"""Generate a :class:`Keypair` object from a secret seed.
:param secret: strkey ed25519 seed, for example: `SB2LHKBL24ITV2Y346BU46XPEL45BDAFOOJLZ6SESCJZ6V5JMP7D6G5X`
:return: A new :class:`Keypair` instance derived by the secret.
:raise: :exc:`Ed25519SecretSeedInvalidError <stellar_sdk.exceptions.Ed25519SecretSeedInvalidError>`:
if ``secret`` is not a valid ed25519 secret seed.
"""
raw_secret = StrKey.decode_ed25519_secret_seed(secret)
return cls.from_raw_ed25519_seed(raw_secret)
[docs] @classmethod
def from_public_key(cls, public_key: str) -> "Keypair":
"""Generate a :class:`Keypair` object from a public key.
:param public_key: strkey ed25519 public key,
for example: `GATPGGOIE6VWADVKD3ER3IFO2IH6DTOA5G535ITB3TT66FZFSIZEAU2B`
:return: A new :class:`Keypair` instance derived by the public key.
:raise: :exc:`Ed25519PublicKeyInvalidError <stellar_sdk.exceptions.Ed25519PublicKeyInvalidError>`:
if ``public_key`` is not a valid ed25519 public key.
"""
key = StrKey.decode_ed25519_public_key(public_key)
return cls.from_raw_ed25519_public_key(key)
[docs] @classmethod
def from_raw_ed25519_seed(cls, raw_seed: bytes) -> "Keypair":
"""Generate a :class:`Keypair` object from ed25519 secret key seed raw bytes.
:param raw_seed: ed25519 secret key seed raw bytes
:return: A new :class:`Keypair` instance derived by the ed25519 secret key seed raw bytes
"""
signing_key = ed25519.SigningKey(raw_seed)
verify_key = signing_key.verify_key
return cls(verify_key, signing_key)
[docs] @classmethod
def from_raw_ed25519_public_key(cls, raw_public_key: bytes) -> "Keypair":
"""Generate a :class:`Keypair` object from ed25519 public key raw bytes.
:param raw_public_key: ed25519 public key raw bytes
:return: A new :class:`Keypair` instance derived by the ed25519 public key raw bytes
"""
verify_key = ed25519.VerifyKey(raw_public_key)
return cls(verify_key)
@property
def secret(self) -> str:
"""Returns secret key associated with this :class:`Keypair` instance
:return: secret key
:raise: :exc:`MissingEd25519SecretSeedError <stellar_sdk.exceptions.MissingEd25519SecretSeedError>`
The :class:`Keypair` does not contain secret seed
"""
if not self.signing_key:
raise MissingEd25519SecretSeedError(
"The keypair does not contain secret seed. Use Keypair.from_secret or "
"Keypair.random to create a new keypair with a secret seed."
)
return StrKey.encode_ed25519_secret_seed(self.raw_secret_key())
@secret.setter
def secret(self, secret: str) -> None:
raise AttributeError(
"Please use `Keypair.from_secret` to generate a new Keypair object."
)
@property
def public_key(self) -> str:
"""Returns public key associated with this :class:`Keypair` instance
:return: public key
"""
return StrKey.encode_ed25519_public_key(self.raw_public_key())
@public_key.setter
def public_key(self, public_key: str) -> None:
raise AttributeError(
"Please use `Keypair.from_public_key` to generate a new Keypair object."
)
[docs] def xdr_public_key(self) -> Xdr.types.PublicKey:
"""
:return: xdr public key
"""
return Xdr.types.PublicKey(Xdr.const.KEY_TYPE_ED25519, bytes(self.verify_key))
def xdr_account_id(self) -> Xdr.types.PublicKey:
return self.xdr_public_key()
[docs] def raw_public_key(self) -> bytes:
"""Returns raw public key.
:return: raw public key
"""
return bytes(self.verify_key)
[docs] def signature_hint(self) -> bytes:
"""Returns signature hint associated with this :class:`Keypair` instance
:return: signature hint
"""
return bytes(self.xdr_account_id().ed25519[-4:])
[docs] def raw_secret_key(self) -> bytes:
"""Returns raw secret key.
:return: raw secret key
"""
return bytes(self.signing_key)
[docs] def can_sign(self) -> bool:
"""Returns `True` if this :class:`Keypair` object contains secret key and can sign.
:return: `True` if this :class:`Keypair` object contains secret key and can sign
"""
return self.signing_key is not None
[docs] def sign(self, data: bytes) -> bytes:
"""Sign the provided data with the keypair's private key.
:param data: The data to sign.
:return: signed bytes
:raise: :exc:`MissingEd25519SecretSeedError <stellar_sdk.exceptions.MissingEd25519SecretSeedError>`:
if :class:`Keypair` does not contain secret seed.
"""
if not self.can_sign():
raise MissingEd25519SecretSeedError(
"The keypair does not contain secret seed. Use Keypair.from_secret or "
"Keypair.random to create a new keypair with a secret seed."
)
return self.signing_key.sign(data).signature
[docs] def verify(self, data: bytes, signature: bytes) -> None:
"""Verify the provided data and signature match this keypair's public key.
:param data: The data that was signed.
:param signature: The signature.
:raise: :exc:`BadSignatureError <stellar_sdk.exceptions.BadSignatureError>`:
if the verification failed and the signature was incorrect.
"""
try:
self.verify_key.verify(data, signature)
except NaclBadSignatureError:
raise BadSignatureError("Signature verification failed.")
[docs] @staticmethod
def generate_mnemonic_phrase(
language: Union[Language, str] = Language.ENGLISH, strength: int = 128
):
"""Generate a mnemonic phrase.
:param language: The language of the mnemonic phrase, defaults to english.
:param strength: The complexity of the mnemonic.
:return: A mnemonic phrase.
"""
mnemonic_phrase = StellarMnemonic(language).generate(strength)
return mnemonic_phrase
[docs] @classmethod
def from_mnemonic_phrase(
cls, mnemonic_phrase: str, passphrase: str = "", index: int = 0
):
"""Generate a :class:`Keypair` object via a mnemonic
phrase.
:param mnemonic_phrase: A unique string used to deterministically generate keypairs.
:param passphrase: An optional passphrase used as part of the salt
during PBKDF2 rounds when generating the seed from the mnemonic.
:param index: The index of the keypair generated by the mnemonic.
This allows for multiple Keypairs to be derived from the same
mnemonic, such as::
>>> from stellar_sdk.keypair import Keypair
>>> mnemonic = 'update hello cry airport drive chunk elite boat shaft sea describe number' # Don't use this mnemonic in practice.
>>> kp1 = Keypair.from_mnemonic_phrase(mnemonic, index=0)
>>> kp2 = Keypair.from_mnemonic_phrase(mnemonic, index=1)
>>> kp3 = Keypair.from_mnemonic_phrase(mnemonic, index=2)
:return: A new :class:`Keypair` instance derived from the mnemonic.
"""
raw_ed25519_seed = StellarMnemonic.to_seed(mnemonic_phrase, passphrase, index)
return cls.from_raw_ed25519_seed(raw_ed25519_seed)
[docs] def sign_decorated(self, data) -> Xdr.types.DecoratedSignature:
"""Sign the provided data with the keypair's private key and returns DecoratedSignature.
:param data: signed bytes
:return: sign decorated
"""
signature = self.sign(data)
hint = self.signature_hint()
return Xdr.types.DecoratedSignature(hint, signature)
def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return NotImplemented # pragma: no cover
return (
self.verify_key == other.verify_key
and self.signing_key == other.signing_key
)
def __str__(self):
return "<Keypair [public_key={public_key}]>".format(public_key=self.public_key)
def _get_key_of_expected_type(key: Any, expected_type: Any) -> Any:
if key is not None and not isinstance(key, expected_type):
raise TypeError(
"The given key_type={} is not of type {}.".format(type(key), expected_type)
)
return key