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