1# Copyright 2018 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.
14
15
16from nacl import exceptions as exc
17from nacl._sodium import ffi, lib
18from nacl.exceptions import ensure
19
20has_crypto_core_ed25519 = bool(lib.PYNACL_HAS_CRYPTO_CORE_ED25519)
21
22crypto_core_ed25519_BYTES = 0
23crypto_core_ed25519_SCALARBYTES = 0
24crypto_core_ed25519_NONREDUCEDSCALARBYTES = 0
25
26if has_crypto_core_ed25519:
27 crypto_core_ed25519_BYTES = lib.crypto_core_ed25519_bytes()
28 crypto_core_ed25519_SCALARBYTES = lib.crypto_core_ed25519_scalarbytes()
29 crypto_core_ed25519_NONREDUCEDSCALARBYTES = (
30 lib.crypto_core_ed25519_nonreducedscalarbytes()
31 )
32
33
34def crypto_core_ed25519_is_valid_point(p: bytes) -> bool:
35 """
36 Check if ``p`` represents a point on the edwards25519 curve, in canonical
37 form, on the main subgroup, and that the point doesn't have a small order.
38
39 :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
40 representing a point on the edwards25519 curve
41 :type p: bytes
42 :return: point validity
43 :rtype: bool
44 :raises nacl.exceptions.UnavailableError: If called when using a
45 minimal build of libsodium.
46 """
47 ensure(
48 has_crypto_core_ed25519,
49 "Not available in minimal build",
50 raising=exc.UnavailableError,
51 )
52
53 ensure(
54 isinstance(p, bytes) and len(p) == crypto_core_ed25519_BYTES,
55 "Point must be a crypto_core_ed25519_BYTES long bytes sequence",
56 raising=exc.TypeError,
57 )
58
59 rc = lib.crypto_core_ed25519_is_valid_point(p)
60 return rc == 1
61
62
63def crypto_core_ed25519_from_uniform(r: bytes) -> bytes:
64 """
65 Maps a 32 bytes vector ``r`` to a point. The point is guaranteed to be on the main subgroup.
66 This function directly exposes the Elligator 2 map, uses the high bit to set
67 the sign of the X coordinate, and the resulting point is multiplied by the cofactor.
68
69 :param r: a :py:data:`.crypto_core_ed25519_BYTES` long bytes
70 sequence representing arbitrary data
71 :type r: bytes
72 :return: a point on the edwards25519 curve main order subgroup, represented as a
73 :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
74 :rtype: bytes
75 :raises nacl.exceptions.UnavailableError: If called when using a
76 minimal build of libsodium.
77 """
78 ensure(
79 has_crypto_core_ed25519,
80 "Not available in minimal build",
81 raising=exc.UnavailableError,
82 )
83
84 ensure(
85 isinstance(r, bytes) and len(r) == crypto_core_ed25519_BYTES,
86 "Integer r must be a {} long bytes sequence".format(
87 "crypto_core_ed25519_BYTES"
88 ),
89 raising=exc.TypeError,
90 )
91
92 p = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)
93
94 rc = lib.crypto_core_ed25519_from_uniform(p, r)
95 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
96
97 return ffi.buffer(p, crypto_core_ed25519_BYTES)[:]
98
99
100def crypto_core_ed25519_add(p: bytes, q: bytes) -> bytes:
101 """
102 Add two points on the edwards25519 curve.
103
104 :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
105 representing a point on the edwards25519 curve
106 :type p: bytes
107 :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
108 representing a point on the edwards25519 curve
109 :type q: bytes
110 :return: a point on the edwards25519 curve represented as
111 a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
112 :rtype: bytes
113 :raises nacl.exceptions.UnavailableError: If called when using a
114 minimal build of libsodium.
115 """
116 ensure(
117 has_crypto_core_ed25519,
118 "Not available in minimal build",
119 raising=exc.UnavailableError,
120 )
121
122 ensure(
123 isinstance(p, bytes)
124 and isinstance(q, bytes)
125 and len(p) == crypto_core_ed25519_BYTES
126 and len(q) == crypto_core_ed25519_BYTES,
127 "Each point must be a {} long bytes sequence".format(
128 "crypto_core_ed25519_BYTES"
129 ),
130 raising=exc.TypeError,
131 )
132
133 r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)
134
135 rc = lib.crypto_core_ed25519_add(r, p, q)
136 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
137
138 return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]
139
140
141def crypto_core_ed25519_sub(p: bytes, q: bytes) -> bytes:
142 """
143 Subtract a point from another on the edwards25519 curve.
144
145 :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
146 representing a point on the edwards25519 curve
147 :type p: bytes
148 :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
149 representing a point on the edwards25519 curve
150 :type q: bytes
151 :return: a point on the edwards25519 curve represented as
152 a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
153 :rtype: bytes
154 :raises nacl.exceptions.UnavailableError: If called when using a
155 minimal build of libsodium.
156 """
157 ensure(
158 has_crypto_core_ed25519,
159 "Not available in minimal build",
160 raising=exc.UnavailableError,
161 )
162
163 ensure(
164 isinstance(p, bytes)
165 and isinstance(q, bytes)
166 and len(p) == crypto_core_ed25519_BYTES
167 and len(q) == crypto_core_ed25519_BYTES,
168 "Each point must be a {} long bytes sequence".format(
169 "crypto_core_ed25519_BYTES"
170 ),
171 raising=exc.TypeError,
172 )
173
174 r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)
175
176 rc = lib.crypto_core_ed25519_sub(r, p, q)
177 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
178
179 return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]
180
181
182def crypto_core_ed25519_scalar_invert(s: bytes) -> bytes:
183 """
184 Return the multiplicative inverse of integer ``s`` modulo ``L``,
185 i.e an integer ``i`` such that ``s * i = 1 (mod L)``, where ``L``
186 is the order of the main subgroup.
187
188 Raises a ``exc.RuntimeError`` if ``s`` is the integer zero.
189
190 :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
191 long bytes sequence representing an integer
192 :type s: bytes
193 :return: an integer represented as a
194 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
195 :rtype: bytes
196 :raises nacl.exceptions.UnavailableError: If called when using a
197 minimal build of libsodium.
198 """
199 ensure(
200 has_crypto_core_ed25519,
201 "Not available in minimal build",
202 raising=exc.UnavailableError,
203 )
204
205 ensure(
206 isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
207 "Integer s must be a {} long bytes sequence".format(
208 "crypto_core_ed25519_SCALARBYTES"
209 ),
210 raising=exc.TypeError,
211 )
212
213 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
214
215 rc = lib.crypto_core_ed25519_scalar_invert(r, s)
216 ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
217
218 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
219
220
221def crypto_core_ed25519_scalar_negate(s: bytes) -> bytes:
222 """
223 Return the integer ``n`` such that ``s + n = 0 (mod L)``, where ``L``
224 is the order of the main subgroup.
225
226 :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
227 long bytes sequence representing an integer
228 :type s: bytes
229 :return: an integer represented as a
230 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
231 :rtype: bytes
232 :raises nacl.exceptions.UnavailableError: If called when using a
233 minimal build of libsodium.
234 """
235 ensure(
236 has_crypto_core_ed25519,
237 "Not available in minimal build",
238 raising=exc.UnavailableError,
239 )
240
241 ensure(
242 isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
243 "Integer s must be a {} long bytes sequence".format(
244 "crypto_core_ed25519_SCALARBYTES"
245 ),
246 raising=exc.TypeError,
247 )
248
249 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
250
251 lib.crypto_core_ed25519_scalar_negate(r, s)
252
253 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
254
255
256def crypto_core_ed25519_scalar_complement(s: bytes) -> bytes:
257 """
258 Return the complement of integer ``s`` modulo ``L``, i.e. an integer
259 ``c`` such that ``s + c = 1 (mod L)``, where ``L`` is the order of
260 the main subgroup.
261
262 :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
263 long bytes sequence representing an integer
264 :type s: bytes
265 :return: an integer represented as a
266 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
267 :rtype: bytes
268 :raises nacl.exceptions.UnavailableError: If called when using a
269 minimal build of libsodium.
270 """
271 ensure(
272 has_crypto_core_ed25519,
273 "Not available in minimal build",
274 raising=exc.UnavailableError,
275 )
276
277 ensure(
278 isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
279 "Integer s must be a {} long bytes sequence".format(
280 "crypto_core_ed25519_SCALARBYTES"
281 ),
282 raising=exc.TypeError,
283 )
284
285 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
286
287 lib.crypto_core_ed25519_scalar_complement(r, s)
288
289 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
290
291
292def crypto_core_ed25519_scalar_add(p: bytes, q: bytes) -> bytes:
293 """
294 Add integers ``p`` and ``q`` modulo ``L``, where ``L`` is the order of
295 the main subgroup.
296
297 :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
298 long bytes sequence representing an integer
299 :type p: bytes
300 :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
301 long bytes sequence representing an integer
302 :type q: bytes
303 :return: an integer represented as a
304 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
305 :rtype: bytes
306 :raises nacl.exceptions.UnavailableError: If called when using a
307 minimal build of libsodium.
308 """
309 ensure(
310 has_crypto_core_ed25519,
311 "Not available in minimal build",
312 raising=exc.UnavailableError,
313 )
314
315 ensure(
316 isinstance(p, bytes)
317 and isinstance(q, bytes)
318 and len(p) == crypto_core_ed25519_SCALARBYTES
319 and len(q) == crypto_core_ed25519_SCALARBYTES,
320 "Each integer must be a {} long bytes sequence".format(
321 "crypto_core_ed25519_SCALARBYTES"
322 ),
323 raising=exc.TypeError,
324 )
325
326 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
327
328 lib.crypto_core_ed25519_scalar_add(r, p, q)
329
330 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
331
332
333def crypto_core_ed25519_scalar_sub(p: bytes, q: bytes) -> bytes:
334 """
335 Subtract integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
336 order of the main subgroup.
337
338 :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
339 long bytes sequence representing an integer
340 :type p: bytes
341 :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
342 long bytes sequence representing an integer
343 :type q: bytes
344 :return: an integer represented as a
345 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
346 :rtype: bytes
347 :raises nacl.exceptions.UnavailableError: If called when using a
348 minimal build of libsodium.
349 """
350 ensure(
351 has_crypto_core_ed25519,
352 "Not available in minimal build",
353 raising=exc.UnavailableError,
354 )
355
356 ensure(
357 isinstance(p, bytes)
358 and isinstance(q, bytes)
359 and len(p) == crypto_core_ed25519_SCALARBYTES
360 and len(q) == crypto_core_ed25519_SCALARBYTES,
361 "Each integer must be a {} long bytes sequence".format(
362 "crypto_core_ed25519_SCALARBYTES"
363 ),
364 raising=exc.TypeError,
365 )
366
367 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
368
369 lib.crypto_core_ed25519_scalar_sub(r, p, q)
370
371 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
372
373
374def crypto_core_ed25519_scalar_mul(p: bytes, q: bytes) -> bytes:
375 """
376 Multiply integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
377 order of the main subgroup.
378
379 :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
380 long bytes sequence representing an integer
381 :type p: bytes
382 :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
383 long bytes sequence representing an integer
384 :type q: bytes
385 :return: an integer represented as a
386 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
387 :rtype: bytes
388 :raises nacl.exceptions.UnavailableError: If called when using a
389 minimal build of libsodium.
390 """
391 ensure(
392 has_crypto_core_ed25519,
393 "Not available in minimal build",
394 raising=exc.UnavailableError,
395 )
396
397 ensure(
398 isinstance(p, bytes)
399 and isinstance(q, bytes)
400 and len(p) == crypto_core_ed25519_SCALARBYTES
401 and len(q) == crypto_core_ed25519_SCALARBYTES,
402 "Each integer must be a {} long bytes sequence".format(
403 "crypto_core_ed25519_SCALARBYTES"
404 ),
405 raising=exc.TypeError,
406 )
407
408 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
409
410 lib.crypto_core_ed25519_scalar_mul(r, p, q)
411
412 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
413
414
415def crypto_core_ed25519_scalar_reduce(s: bytes) -> bytes:
416 """
417 Reduce integer ``s`` to ``s`` modulo ``L``, where ``L`` is the order
418 of the main subgroup.
419
420 :param s: a :py:data:`.crypto_core_ed25519_NONREDUCEDSCALARBYTES`
421 long bytes sequence representing an integer
422 :type s: bytes
423 :return: an integer represented as a
424 :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
425 :rtype: bytes
426 :raises nacl.exceptions.UnavailableError: If called when using a
427 minimal build of libsodium.
428 """
429 ensure(
430 has_crypto_core_ed25519,
431 "Not available in minimal build",
432 raising=exc.UnavailableError,
433 )
434
435 ensure(
436 isinstance(s, bytes)
437 and len(s) == crypto_core_ed25519_NONREDUCEDSCALARBYTES,
438 "Integer s must be a {} long bytes sequence".format(
439 "crypto_core_ed25519_NONREDUCEDSCALARBYTES"
440 ),
441 raising=exc.TypeError,
442 )
443
444 r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
445
446 lib.crypto_core_ed25519_scalar_reduce(r, s)
447
448 return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]