# coding: utf-8
import base64
import os
import warnings
from .base58 import b58decode_check, b58encode_check
from .stellarxdr import Xdr
from .utils import encode_check, StellarMnemonic, \
is_valid_address, is_valid_secret_key
from .exceptions import MissingSigningKeyError, BadSignatureError, NotValidParamError
# 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_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)
verifying_key = ed25519.VerifyingKey(public_key)
return cls(verifying_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)