1# Copyright 2013 Donald Stufft and individual contributors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from __future__ import annotations
15
16import nacl.bindings
17from nacl import encoding
18from nacl import exceptions as exc
19from nacl.public import (
20 PrivateKey as _Curve25519_PrivateKey,
21)
22from nacl.public import (
23 PublicKey as _Curve25519_PublicKey,
24)
25from nacl.utils import StringFixer, random
26
27
28class SignedMessage(bytes):
29 """
30 A bytes subclass that holds a message that has been signed by a
31 :class:`SigningKey`.
32 """
33
34 _signature: bytes
35 _message: bytes
36
37 @classmethod
38 def _from_parts(
39 cls, signature: bytes, message: bytes, combined: bytes
40 ) -> SignedMessage:
41 obj = cls(combined)
42 obj._signature = signature
43 obj._message = message
44 return obj
45
46 @property
47 def signature(self) -> bytes:
48 """
49 The signature contained within the :class:`SignedMessage`.
50 """
51 return self._signature
52
53 @property
54 def message(self) -> bytes:
55 """
56 The message contained within the :class:`SignedMessage`.
57 """
58 return self._message
59
60
61class VerifyKey(encoding.Encodable, StringFixer):
62 """
63 The public key counterpart to an Ed25519 SigningKey for producing digital
64 signatures.
65
66 :param key: [:class:`bytes`] Serialized Ed25519 public key
67 :param encoder: A class that is able to decode the `key`
68 """
69
70 def __init__(
71 self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
72 ):
73 # Decode the key
74 key = encoder.decode(key)
75 if not isinstance(key, bytes):
76 raise exc.TypeError("VerifyKey must be created from 32 bytes")
77
78 if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES:
79 raise exc.ValueError(
80 f"The key must be exactly {nacl.bindings.crypto_sign_PUBLICKEYBYTES} bytes long",
81 )
82
83 self._key = key
84
85 def __bytes__(self) -> bytes:
86 return self._key
87
88 def __hash__(self) -> int:
89 return hash(bytes(self))
90
91 def __eq__(self, other: object) -> bool:
92 if not isinstance(other, self.__class__):
93 return False
94 return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
95
96 def __ne__(self, other: object) -> bool:
97 return not (self == other)
98
99 def verify(
100 self,
101 smessage: bytes,
102 signature: bytes | None = None,
103 encoder: encoding.Encoder = encoding.RawEncoder,
104 ) -> bytes:
105 """
106 Verifies the signature of a signed message, returning the message
107 if it has not been tampered with else raising
108 :class:`~nacl.exceptions.BadSignatureError`.
109
110 :param smessage: [:class:`bytes`] Either the original messaged or a
111 signature and message concated together.
112 :param signature: [:class:`bytes`] If an unsigned message is given for
113 smessage then the detached signature must be provided.
114 :param encoder: A class that is able to decode the secret message and
115 signature.
116 :rtype: :class:`bytes`
117 """
118 if signature is not None:
119 # If we were given the message and signature separately, validate
120 # signature size and combine them.
121 if not isinstance(signature, bytes):
122 raise exc.TypeError(
123 "Verification signature must be created from "
124 f"{nacl.bindings.crypto_sign_BYTES} bytes",
125 )
126
127 if len(signature) != nacl.bindings.crypto_sign_BYTES:
128 raise exc.ValueError(
129 "The signature must be exactly "
130 f"{nacl.bindings.crypto_sign_BYTES} bytes long",
131 )
132
133 smessage = signature + encoder.decode(smessage)
134 else:
135 # Decode the signed message
136 smessage = encoder.decode(smessage)
137
138 return nacl.bindings.crypto_sign_open(smessage, self._key)
139
140 def to_curve25519_public_key(self) -> _Curve25519_PublicKey:
141 """
142 Converts a :class:`~nacl.signing.VerifyKey` to a
143 :class:`~nacl.public.PublicKey`
144
145 :rtype: :class:`~nacl.public.PublicKey`
146 """
147 raw_pk = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(self._key)
148 return _Curve25519_PublicKey(raw_pk)
149
150
151class SigningKey(encoding.Encodable, StringFixer):
152 """
153 Private key for producing digital signatures using the Ed25519 algorithm.
154
155 Signing keys are produced from a 32-byte (256-bit) random seed value. This
156 value can be passed into the :class:`~nacl.signing.SigningKey` as a
157 :func:`bytes` whose length is 32.
158
159 .. warning:: This **must** be protected and remain secret. Anyone who knows
160 the value of your :class:`~nacl.signing.SigningKey` or it's seed can
161 masquerade as you.
162
163 :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
164 :param encoder: A class that is able to decode the seed
165
166 :ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
167 (i.e. public) key that corresponds with this signing key.
168 """
169
170 def __init__(
171 self,
172 seed: bytes,
173 encoder: encoding.Encoder = encoding.RawEncoder,
174 ):
175 # Decode the seed
176 seed = encoder.decode(seed)
177 if not isinstance(seed, bytes):
178 raise exc.TypeError(
179 "SigningKey must be created from a 32 byte seed"
180 )
181
182 # Verify that our seed is the proper size
183 if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES:
184 raise exc.ValueError(
185 "The seed must be exactly "
186 f"{nacl.bindings.crypto_sign_SEEDBYTES} bytes long"
187 )
188
189 public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)
190
191 self._seed = seed
192 self._signing_key = secret_key
193 self.verify_key = VerifyKey(public_key)
194
195 def __bytes__(self) -> bytes:
196 return self._seed
197
198 def __hash__(self) -> int:
199 return hash(bytes(self))
200
201 def __eq__(self, other: object) -> bool:
202 if not isinstance(other, self.__class__):
203 return False
204 return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
205
206 def __ne__(self, other: object) -> bool:
207 return not (self == other)
208
209 @classmethod
210 def generate(cls) -> SigningKey:
211 """
212 Generates a random :class:`~nacl.signing.SigningKey` object.
213
214 :rtype: :class:`~nacl.signing.SigningKey`
215 """
216 return cls(
217 random(nacl.bindings.crypto_sign_SEEDBYTES),
218 encoder=encoding.RawEncoder,
219 )
220
221 def sign(
222 self,
223 message: bytes,
224 encoder: encoding.Encoder = encoding.RawEncoder,
225 ) -> SignedMessage:
226 """
227 Sign a message using this key.
228
229 :param message: [:class:`bytes`] The data to be signed.
230 :param encoder: A class that is used to encode the signed message.
231 :rtype: :class:`~nacl.signing.SignedMessage`
232 """
233 raw_signed = nacl.bindings.crypto_sign(message, self._signing_key)
234
235 crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
236 signature = encoder.encode(raw_signed[:crypto_sign_BYTES])
237 message = encoder.encode(raw_signed[crypto_sign_BYTES:])
238 signed = encoder.encode(raw_signed)
239
240 return SignedMessage._from_parts(signature, message, signed)
241
242 def to_curve25519_private_key(self) -> _Curve25519_PrivateKey:
243 """
244 Converts a :class:`~nacl.signing.SigningKey` to a
245 :class:`~nacl.public.PrivateKey`
246
247 :rtype: :class:`~nacl.public.PrivateKey`
248 """
249 sk = self._signing_key
250 raw_private = nacl.bindings.crypto_sign_ed25519_sk_to_curve25519(sk)
251 return _Curve25519_PrivateKey(raw_private)