Source code for stellar_base.keypair

# coding: utf-8
import base64
import os
import warnings

from .base58 import b58decode_check, b58encode_check
from .exceptions import MissingSigningKeyError, BadSignatureError, NotValidParamError
from .stellarxdr import Xdr
from .utils import encode_check, StellarMnemonic, \
    is_valid_address, is_valid_secret_key

# noinspection PyBroadException
try:
    # noinspection PyUnresolvedReferences
    import ed25519
except ImportError:  # pragma: no cover
    from pure25519 import ed25519_oop as ed25519  # pragma: no cover

import hashlib


def _get_key_of_expected_type(key, expected_type):
    if key is not None and not isinstance(key, expected_type):
        raise NotValidParamError("The given key_type={}) is not of type {}.".format(type(key), expected_type))
    return key


[docs]class Keypair(object): """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.deterministic` * :meth:`Keypair.from_seed` * :meth:`Keypair.from_address` :param verifying_key: The verifying (public) Ed25519 key in the keypair. :type verifying_key: ed25519.VerifyingKey :param signing_key: The signing (private) Ed25519 key in the keypair. :type signing_key: ed25519.SigningKey """ def __init__(self, verifying_key, signing_key=None): self.verifying_key = _get_key_of_expected_type(verifying_key, ed25519.VerifyingKey) self.signing_key = _get_key_of_expected_type(signing_key, ed25519.SigningKey)
[docs] @classmethod def deterministic(cls, mnemonic, passphrase='', lang='english', index=0): """Generate a :class:`Keypair` object via a deterministic phrase. Using a mnemonic, such as one generated from :class:`StellarMnemonic`, generate a new keypair deterministically. Uses :class:`StellarMnemonic` internally to generate the seed from the mnemonic, using PBKDF2. :param str mnemonic: A unique string used to deterministically generate keypairs. :param str passphrase: An optional passphrase used as part of the salt during PBKDF2 rounds when generating the seed from the mnemonic. :param str lang: The language of the mnemonic, defaults to english. :param int 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_base.keypair import Keypair >>> m = 'update hello cry airport drive chunk elite boat shaft sea describe number' # Don't use this mnemonic in practice. >>> kp1 = Keypair.deterministic(m, lang='english', index=0) >>> kp2 = Keypair.deterministic(m, lang='english', index=1) >>> kp3 = Keypair.deterministic(m, lang='english', index=2) :return: A new :class:`Keypair` instance derived from the mnemonic. """ sm = StellarMnemonic(lang) seed = sm.to_seed(mnemonic, passphrase=passphrase, index=index) return cls.from_raw_seed(seed)
[docs] @classmethod def random(cls): """Generate a :class:`Keypair` object via a randomly generated seed.""" seed = os.urandom(32) return cls.from_raw_seed(seed)
[docs] @classmethod def from_seed(cls, seed): """Generate a :class:`Keypair` object via a strkey encoded seed. :param str seed: A base32 encoded secret seed string encoded as described in :func:`encode_check`. :return: A new :class:`Keypair` instance derived by the secret seed. """ raw_seed = is_valid_secret_key(seed) return cls.from_raw_seed(raw_seed)
[docs] @classmethod def from_raw_seed(cls, raw_seed): """Generate a :class:`Keypair` object via a sequence of bytes. Typically these bytes are random, such as the usage of :func:`os.urandom` in :meth:`Keypair.random`. However this class method allows you to use an arbitrary sequence of bytes, provided the sequence is 32 bytes long. :param bytes raw_seed: A bytes object used as the seed for generating the keypair. :return: A new :class:`Keypair` derived by the raw secret seed. """ signing_key = ed25519.SigningKey(raw_seed) verifying_key = signing_key.get_verifying_key() return cls(verifying_key, signing_key)
[docs] @classmethod def from_raw_address(cls, raw_address): """Generate a :class:`Keypair` object via a sequence of bytes. :param bytes raw_address: A bytes object used as the address for generating the keypair. :return: A new :class:`Keypair` derived by the raw public key. """ verifying_key = ed25519.VerifyingKey(raw_address) return cls(verifying_key)
[docs] @classmethod def from_base58_seed(cls, base58_seed): """Generate a :class:`Keypair` object via Base58 encoded seed. .. deprecated:: 0.1.7 Base58 address encoding is DEPRECATED! Use this method only for transition to strkey encoding. :param str base58_seed: A base58 encoded encoded secret seed. :return: A new :class:`Keypair` derived from the secret seed. """ warnings.warn( "Base58 address encoding is DEPRECATED! Use this method only for " "transition to strkey encoding.", DeprecationWarning) raw_seed = b58decode_check(base58_seed)[1:] return cls.from_raw_seed(raw_seed)
[docs] @classmethod def from_address(cls, address): """Generate a :class:`Keypair` object via a strkey encoded public key. :param str address: A base32 encoded public key encoded as described in :func:`encode_check` :return: A new :class:`Keypair` with only a verifying (public) key. """ public_key = is_valid_address(address) return cls.from_raw_address(public_key)
# TODO: Make some of the following functions properties?
[docs] def account_xdr_object(self): """Create PublicKey XDR object via public key bytes. :return: Serialized XDR of PublicKey type. """ return Xdr.types.PublicKey(Xdr.const.KEY_TYPE_ED25519, self.verifying_key.to_bytes())
[docs] def xdr(self): """Generate base64 encoded XDR PublicKey object. Return a base64 encoded PublicKey XDR object, for sending over the wire when interacting with stellar. :return: The base64 encoded PublicKey XDR structure. """ kp = Xdr.StellarXDRPacker() kp.pack_PublicKey(self.account_xdr_object()) return base64.b64encode(kp.get_buffer())
[docs] def public_key(self): """See :meth:`Keypair.account_xdr_object`.""" return self.account_xdr_object()
[docs] def raw_public_key(self): """Get the bytes that comprise the verifying (public) key. :return: The verifying key's bytes. """ return self.verifying_key.to_bytes()
[docs] def raw_seed(self): """Get the bytes of the signing key's seed. :return: The signing key's secret seed as a byte sequence. :rtype: bytes """ return self.signing_key.to_seed()
[docs] def address(self): """Get the public key encoded as a strkey. See :func:`encode_check` for more details on the strkey encoding process. :return: The public key encoded as a strkey. :rtype: bytes """ return encode_check('account', self.raw_public_key())
[docs] def seed(self): """Get the secret seed encoded as a strkey. See :func:`encode_check` for more details on the strkey encoding process. :return: The secret seed encoded as a strkey. :rtype: bytes """ return encode_check('seed', self.raw_seed())
[docs] def sign(self, data): """Sign a bytes-like object using the signing (private) key. :param bytes data: The data to sign :return: The signed data :rtype: bytes """ if self.signing_key is None: raise MissingSigningKeyError("KeyPair does not contain secret key. " "Use Keypair.from_seed method to create a new keypair with a secret key.") return self.signing_key.sign(data)
[docs] def verify(self, data, signature): """Verify the signature of a sequence of bytes. Verify the signature of a sequence of bytes using the verifying (public) key and the data that was originally signed, otherwise throws an exception. :param bytes data: A sequence of bytes that were previously signed by the private key associated with this verifying key. :param bytes signature: A sequence of bytes that comprised the signature for the corresponding data. """ try: return self.verifying_key.verify(signature, data) except ed25519.BadSignatureError: raise BadSignatureError("Signature verification failed.")
[docs] def sign_decorated(self, data): """Sign a bytes-like object and return the decorated signature. Sign a bytes-like object by signing the data using the signing (private) key, and return a decorated signature, which includes the last four bytes of the public key as a signature hint to go along with the signature as an XDR DecoratedSignature object. :param bytes data: A sequence of bytes to sign, typically a transaction. """ signature = self.sign(data) hint = self.signature_hint() return Xdr.types.DecoratedSignature(hint, signature)
[docs] def signature_hint(self): """Get the signature hint derived from the public key in this :class:`Keypair`. Get the last four bytes of the public key to be used as a signature hint when utilizing decorated signatures. """ return bytes(self.public_key().ed25519[-4:])
def to_old_address(self): rv = hashlib.new('sha256', self.raw_public_key()).digest() rv = hashlib.new('ripemd160', rv).digest() rv = chr(0).encode() + rv # v += hashlib.new( # 'sha256', hashlib.new('sha256', rv).digest()).digest()[0:4] return b58encode_check(rv) def to_old_seed(self): seed = chr(33).encode() + self.raw_seed() return b58encode_check(seed) def __eq__(self, other): if not isinstance(other, Keypair): return False return self.signing_key == other.signing_key and self.verifying_key == other.verifying_key