File: identity_key_pair.py

package info (click to toggle)
python-x3dh 1.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 356 kB
  • sloc: python: 1,259; makefile: 15
file content (192 lines) | stat: -rw-r--r-- 5,535 bytes parent folder | download
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