1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
from __future__ import annotations
from abc import ABC, abstractmethod
import json
from typing import cast
from typing_extensions import assert_never
import xeddsa
from .migrations import parse_identity_key_pair_model
from .models import IdentityKeyPairModel
from .types import JSONObject, SecretType
__all__ = [
"IdentityKeyPair",
"IdentityKeyPairPriv",
"IdentityKeyPairSeed"
]
class IdentityKeyPair(ABC):
"""
An identity key pair.
There are following requirements for the identity key pair:
* It must be able to create and verify Ed25519-compatible signatures.
* It must be able to perform X25519-compatible Diffie-Hellman key agreements.
There are at least two different kinds of key pairs that can fulfill these requirements: Ed25519 key pairs
and Curve25519 key pairs. The birational equivalence of both curves can be used to "convert" one pair to
the other.
Both types of key pairs share the same private key, however instead of a private key, a seed can be used
which the private key is derived from using SHA-512. This is standard practice for Ed25519, where the
other 32 bytes of the SHA-512 seed hash are used as a nonce during signing. If a new key pair has to be
generated, this implementation generates a seed.
"""
@property
def model(self) -> IdentityKeyPairModel:
"""
Returns:
The internal state of this :class:`IdentityKeyPair` as a pydantic model.
"""
return IdentityKeyPairModel(secret=self.secret, secret_type=self.secret_type)
@property
def json(self) -> JSONObject:
"""
Returns:
The internal state of this :class:`IdentityKeyPair` as a JSON-serializable Python object.
"""
return cast(JSONObject, json.loads(self.model.json()))
@staticmethod
def from_model(model: IdentityKeyPairModel) -> "IdentityKeyPair":
"""
Args:
model: The pydantic model holding the internal state of an :class:`IdentityKeyPair`, as produced
by :attr:`model`.
Returns:
A configured instance of :class:`IdentityKeyPair`, with internal state restored from the model.
Warning:
Migrations are not provided via the :attr:`model`/:meth:`from_model` API. Use
:attr:`json`/:meth:`from_json` instead. Refer to :ref:`serialization_and_migration` in the
documentation for details.
"""
if model.secret_type is SecretType.PRIV:
return IdentityKeyPairPriv(model.secret)
if model.secret_type is SecretType.SEED:
return IdentityKeyPairSeed(model.secret)
return assert_never(model.secret_type)
@staticmethod
def from_json(serialized: JSONObject) -> "IdentityKeyPair":
"""
Args:
serialized: A JSON-serializable Python object holding the internal state of an
:class:`IdentityKeyPair`, as produced by :attr:`json`.
Returns:
A configured instance of :class:`IdentityKeyPair`, with internal state restored from the
serialized data.
"""
return IdentityKeyPair.from_model(parse_identity_key_pair_model(serialized))
@property
@abstractmethod
def secret_type(self) -> SecretType:
"""
Returns:
The type of secret used by this identity key (i.e. a seed or private key).
"""
@property
@abstractmethod
def secret(self) -> bytes:
"""
Returns:
The secret used by this identity key, i.e. the seed or private key.
"""
@abstractmethod
def as_priv(self) -> "IdentityKeyPairPriv":
"""
Returns:
An :class:`IdentityKeyPairPriv` derived from this instance, or the instance itself if it already
is an :class:`IdentityKeyPairPriv`.
"""
class IdentityKeyPairPriv(IdentityKeyPair):
"""
An :class:`IdentityKeyPair` represented by a Curve25519/Ed25519 private key.
"""
def __init__(self, priv: bytes) -> None:
"""
Args:
priv: The Curve25519/Ed25519 private key.
"""
if len(priv) != 32:
raise ValueError("Expected the private key to be 32 bytes long.")
self.__priv = priv
@property
def secret_type(self) -> SecretType:
return SecretType.PRIV
@property
def secret(self) -> bytes:
return self.priv
def as_priv(self) -> "IdentityKeyPairPriv":
return self
@property
def priv(self) -> bytes:
"""
Returns:
The Curve25519/Ed25519 private key.
"""
return self.__priv
class IdentityKeyPairSeed(IdentityKeyPair):
"""
An :class:`IdentityKeyPair` represented by a Curve25519/Ed25519 seed.
"""
def __init__(self, seed: bytes) -> None:
"""
Args:
seed: The Curve25519/Ed25519 seed.
"""
if len(seed) != 32:
raise ValueError("Expected the seed to be 32 bytes long.")
self.__seed = seed
@property
def secret_type(self) -> SecretType:
return SecretType.SEED
@property
def secret(self) -> bytes:
return self.seed
def as_priv(self) -> "IdentityKeyPairPriv":
return IdentityKeyPairPriv(xeddsa.seed_to_priv(self.__seed))
@property
def seed(self) -> bytes:
"""
Returns:
The Curve25519/Ed25519 seed.
"""
return self.__seed
|