1use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
2use subtle::ConstantTimeEq;
3
4use crate::constants::{
5 CRYPTO_CORE_ED25519_BYTES, CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_CORE_HCHACHA20_KEYBYTES,
6 CRYPTO_CORE_HCHACHA20_OUTPUTBYTES, CRYPTO_CORE_HSALSA20_INPUTBYTES,
7 CRYPTO_CORE_HSALSA20_KEYBYTES, CRYPTO_CORE_HSALSA20_OUTPUTBYTES, CRYPTO_SCALARMULT_BYTES,
8 CRYPTO_SCALARMULT_SCALARBYTES,
9};
10use crate::error::Error;
11use crate::scalarmult_curve25519::{
12 crypto_scalarmult_curve25519, crypto_scalarmult_curve25519_base,
13};
14use crate::types::*;
15use crate::utils::load_u32_le;
16
17pub type HChaCha20Input = [u8; CRYPTO_CORE_HCHACHA20_INPUTBYTES];
19pub type HChaCha20Key = [u8; CRYPTO_CORE_HCHACHA20_KEYBYTES];
21pub type HChaCha20Output = [u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
23pub type HSalsa20Input = [u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
25pub type HSalsa20Key = [u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
27pub type HSalsa20Output = [u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
29pub type Ed25519Point = [u8; CRYPTO_CORE_ED25519_BYTES];
31
32pub fn crypto_scalarmult_base(
36 q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
37 n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
38) {
39 crypto_scalarmult_curve25519_base(q, n)
40}
41
42pub fn crypto_scalarmult(
52 q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
53 n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
54 p: &[u8; CRYPTO_SCALARMULT_BYTES],
55) -> Result<(), Error> {
56 crypto_scalarmult_curve25519(q, n, p);
57
58 if q.ct_eq(&[0u8; CRYPTO_SCALARMULT_BYTES]).into() {
59 Err(Error::invalid_key(crate::ErrorContext::Curve25519PublicKey))
60 } else {
61 Ok(())
62 }
63}
64
65#[inline]
66fn chacha20_round(x: &mut u32, y: &u32, z: &mut u32, rot: u32) {
67 *x = x.wrapping_add(*y);
68 *z = (*z ^ *x).rotate_left(rot);
69}
70
71#[inline]
72fn chacha20_quarterround(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
73 chacha20_round(a, b, d, 16);
74 chacha20_round(c, d, b, 12);
75 chacha20_round(a, b, d, 8);
76 chacha20_round(c, d, b, 7);
77}
78
79pub fn crypto_core_hchacha20(
83 output: &mut HChaCha20Output,
84 input: &HChaCha20Input,
85 key: &HChaCha20Key,
86 constants: Option<(u32, u32, u32, u32)>,
87) {
88 let input = input.as_array();
89 let key = key.as_array();
90 let (mut x0, mut x1, mut x2, mut x3) =
91 constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
92 let (
93 mut x4,
94 mut x5,
95 mut x6,
96 mut x7,
97 mut x8,
98 mut x9,
99 mut x10,
100 mut x11,
101 mut x12,
102 mut x13,
103 mut x14,
104 mut x15,
105 ) = (
106 load_u32_le(&key[0..4]),
107 load_u32_le(&key[4..8]),
108 load_u32_le(&key[8..12]),
109 load_u32_le(&key[12..16]),
110 load_u32_le(&key[16..20]),
111 load_u32_le(&key[20..24]),
112 load_u32_le(&key[24..28]),
113 load_u32_le(&key[28..32]),
114 load_u32_le(&input[0..4]),
115 load_u32_le(&input[4..8]),
116 load_u32_le(&input[8..12]),
117 load_u32_le(&input[12..16]),
118 );
119
120 for _ in 0..10 {
121 chacha20_quarterround(&mut x0, &mut x4, &mut x8, &mut x12);
122 chacha20_quarterround(&mut x1, &mut x5, &mut x9, &mut x13);
123 chacha20_quarterround(&mut x2, &mut x6, &mut x10, &mut x14);
124 chacha20_quarterround(&mut x3, &mut x7, &mut x11, &mut x15);
125 chacha20_quarterround(&mut x0, &mut x5, &mut x10, &mut x15);
126 chacha20_quarterround(&mut x1, &mut x6, &mut x11, &mut x12);
127 chacha20_quarterround(&mut x2, &mut x7, &mut x8, &mut x13);
128 chacha20_quarterround(&mut x3, &mut x4, &mut x9, &mut x14);
129 }
130
131 output[0..4].copy_from_slice(&x0.to_le_bytes());
132 output[4..8].copy_from_slice(&x1.to_le_bytes());
133 output[8..12].copy_from_slice(&x2.to_le_bytes());
134 output[12..16].copy_from_slice(&x3.to_le_bytes());
135 output[16..20].copy_from_slice(&x12.to_le_bytes());
136 output[20..24].copy_from_slice(&x13.to_le_bytes());
137 output[24..28].copy_from_slice(&x14.to_le_bytes());
138 output[28..32].copy_from_slice(&x15.to_le_bytes());
139}
140
141pub fn crypto_core_ed25519_is_valid_point(p: &Ed25519Point) -> bool {
164 let Some(point) = decompress_canonical_ed25519_point(p) else {
165 return false;
166 };
167
168 !point.is_small_order() && point.is_torsion_free()
169}
170
171pub(crate) fn decompress_canonical_ed25519_point(p: &Ed25519Point) -> Option<EdwardsPoint> {
177 let compressed = CompressedEdwardsY(*p);
178 let point = compressed.decompress()?;
179
180 if point.compress() == compressed {
181 Some(point)
182 } else {
183 None
184 }
185}
186
187#[inline]
188fn salsa20_rotl32(x: u32, y: u32, rot: u32) -> u32 {
189 x.wrapping_add(y).rotate_left(rot)
190}
191
192pub fn crypto_core_hsalsa20(
196 output: &mut HSalsa20Output,
197 input: &HSalsa20Input,
198 key: &HSalsa20Key,
199 constants: Option<(u32, u32, u32, u32)>,
200) {
201 let (mut x0, mut x5, mut x10, mut x15) =
202 constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
203 let (
204 mut x1,
205 mut x2,
206 mut x3,
207 mut x4,
208 mut x11,
209 mut x12,
210 mut x13,
211 mut x14,
212 mut x6,
213 mut x7,
214 mut x8,
215 mut x9,
216 ) = (
217 load_u32_le(&key[0..4]),
218 load_u32_le(&key[4..8]),
219 load_u32_le(&key[8..12]),
220 load_u32_le(&key[12..16]),
221 load_u32_le(&key[16..20]),
222 load_u32_le(&key[20..24]),
223 load_u32_le(&key[24..28]),
224 load_u32_le(&key[28..32]),
225 load_u32_le(&input[0..4]),
226 load_u32_le(&input[4..8]),
227 load_u32_le(&input[8..12]),
228 load_u32_le(&input[12..16]),
229 );
230
231 for _ in (0..20).step_by(2) {
232 x4 ^= salsa20_rotl32(x0, x12, 7);
233 x8 ^= salsa20_rotl32(x4, x0, 9);
234 x12 ^= salsa20_rotl32(x8, x4, 13);
235 x0 ^= salsa20_rotl32(x12, x8, 18);
236 x9 ^= salsa20_rotl32(x5, x1, 7);
237 x13 ^= salsa20_rotl32(x9, x5, 9);
238 x1 ^= salsa20_rotl32(x13, x9, 13);
239 x5 ^= salsa20_rotl32(x1, x13, 18);
240 x14 ^= salsa20_rotl32(x10, x6, 7);
241 x2 ^= salsa20_rotl32(x14, x10, 9);
242 x6 ^= salsa20_rotl32(x2, x14, 13);
243 x10 ^= salsa20_rotl32(x6, x2, 18);
244 x3 ^= salsa20_rotl32(x15, x11, 7);
245 x7 ^= salsa20_rotl32(x3, x15, 9);
246 x11 ^= salsa20_rotl32(x7, x3, 13);
247 x15 ^= salsa20_rotl32(x11, x7, 18);
248 x1 ^= salsa20_rotl32(x0, x3, 7);
249 x2 ^= salsa20_rotl32(x1, x0, 9);
250 x3 ^= salsa20_rotl32(x2, x1, 13);
251 x0 ^= salsa20_rotl32(x3, x2, 18);
252 x6 ^= salsa20_rotl32(x5, x4, 7);
253 x7 ^= salsa20_rotl32(x6, x5, 9);
254 x4 ^= salsa20_rotl32(x7, x6, 13);
255 x5 ^= salsa20_rotl32(x4, x7, 18);
256 x11 ^= salsa20_rotl32(x10, x9, 7);
257 x8 ^= salsa20_rotl32(x11, x10, 9);
258 x9 ^= salsa20_rotl32(x8, x11, 13);
259 x10 ^= salsa20_rotl32(x9, x8, 18);
260 x12 ^= salsa20_rotl32(x15, x14, 7);
261 x13 ^= salsa20_rotl32(x12, x15, 9);
262 x14 ^= salsa20_rotl32(x13, x12, 13);
263 x15 ^= salsa20_rotl32(x14, x13, 18);
264 }
265
266 output[0..4].copy_from_slice(&x0.to_le_bytes());
267 output[4..8].copy_from_slice(&x5.to_le_bytes());
268 output[8..12].copy_from_slice(&x10.to_le_bytes());
269 output[12..16].copy_from_slice(&x15.to_le_bytes());
270 output[16..20].copy_from_slice(&x6.to_le_bytes());
271 output[20..24].copy_from_slice(&x7.to_le_bytes());
272 output[24..28].copy_from_slice(&x8.to_le_bytes());
273 output[28..32].copy_from_slice(&x9.to_le_bytes());
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::classic::crypto_sign::crypto_sign_keypair;
280
281 #[test]
282 fn test_crypto_core_ed25519_is_valid_point() {
283 let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
284 assert!(crypto_core_ed25519_is_valid_point(&basepoint));
285
286 let mut negative_basepoint = basepoint;
287 negative_basepoint[31] |= 0x80;
288 assert!(
289 crypto_core_ed25519_is_valid_point(&negative_basepoint),
290 "the high bit is a valid x-coordinate sign bit"
291 );
292
293 let identity = {
294 let mut point = [0u8; CRYPTO_CORE_ED25519_BYTES];
295 point[0] = 1;
296 point
297 };
298 assert!(!crypto_core_ed25519_is_valid_point(&identity));
299
300 let noncanonical_identity = {
301 let mut point = [0xff; CRYPTO_CORE_ED25519_BYTES];
302 point[0] = 0xee;
303 point[31] = 0x7f;
304 point
305 };
306 assert!(
307 decompress_canonical_ed25519_point(&noncanonical_identity).is_none(),
308 "p + 1 must not be accepted as an alternate encoding of the identity"
309 );
310 assert!(!crypto_core_ed25519_is_valid_point(&noncanonical_identity));
311
312 let torsion = curve25519_dalek::constants::EIGHT_TORSION[1];
313 assert!(torsion.is_small_order());
314 assert!(!crypto_core_ed25519_is_valid_point(
315 &torsion.compress().to_bytes()
316 ));
317
318 let mixed_order = curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion;
319 assert!(!mixed_order.is_small_order());
320 assert!(!mixed_order.is_torsion_free());
321 assert!(!crypto_core_ed25519_is_valid_point(
322 &mixed_order.compress().to_bytes()
323 ));
324
325 let mut point_not_on_curve = [0u8; CRYPTO_CORE_ED25519_BYTES];
326 point_not_on_curve[0] = 2;
327 assert!(!crypto_core_ed25519_is_valid_point(&point_not_on_curve));
328 assert!(!crypto_core_ed25519_is_valid_point(
329 &[0u8; CRYPTO_CORE_ED25519_BYTES]
330 ));
331 }
332
333 #[test]
334 fn test_generated_ed25519_keys_are_valid_points() {
335 for _ in 0..25 {
336 let (ed25519_pk, _) = crypto_sign_keypair();
337 assert!(crypto_core_ed25519_is_valid_point(&ed25519_pk));
338 }
339 }
340
341 #[test]
342 fn test_crypto_core_ed25519_rejects_legacy_libsodium_mixed_order_points() {
343 let mut y_is_nine = [0u8; CRYPTO_CORE_ED25519_BYTES];
344 y_is_nine[0] = 9;
345
346 let mut order_two_coset = [0x99; CRYPTO_CORE_ED25519_BYTES];
349 order_two_coset[0] = 0x95;
350
351 for point in [y_is_nine, order_two_coset] {
352 let decoded = decompress_canonical_ed25519_point(&point)
353 .expect("regression vector must be a canonical curve point");
354 assert!(!decoded.is_small_order());
355 assert!(!decoded.is_torsion_free());
356 assert!(!crypto_core_ed25519_is_valid_point(&point));
357 }
358 }
359
360 #[test]
361 fn test_crypto_scalarmult_rejects_low_order_points() {
362 let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
363 let mut one = [0u8; CRYPTO_SCALARMULT_BYTES];
364 one[0] = 1;
365
366 for public_key in [[0u8; CRYPTO_SCALARMULT_BYTES], one] {
367 let mut shared_secret = [0xa5; CRYPTO_SCALARMULT_BYTES];
368 crypto_scalarmult(&mut shared_secret, &scalar, &public_key)
369 .expect_err("low-order public key must be rejected");
370 assert_eq!(shared_secret, [0u8; CRYPTO_SCALARMULT_BYTES]);
371 }
372 }
373
374 #[test]
375 fn test_crypto_scalarmult_ignores_public_key_high_bit() {
376 let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
377 let mut canonical = [0u8; CRYPTO_SCALARMULT_BYTES];
378 canonical[0] = 9;
379 let mut high_bit_set = canonical;
380 high_bit_set[CRYPTO_SCALARMULT_BYTES - 1] = 0x80;
381 let mut canonical_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
382 let mut high_bit_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
383
384 crypto_scalarmult(&mut canonical_secret, &scalar, &canonical).unwrap();
385 crypto_scalarmult(&mut high_bit_secret, &scalar, &high_bit_set).unwrap();
386
387 assert_eq!(canonical_secret, high_bit_secret);
388 }
389
390 #[cfg(dryoc_native_tests)]
391 mod native_tests {
392 use super::*;
393 use crate::classic::crypto_box::*;
394
395 #[test]
396 fn test_crypto_core_ed25519_compatibility_for_version_stable_points() {
397 use libsodium_sys::crypto_core_ed25519_is_valid_point as sodium_is_valid_point;
398
399 let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
405 let mut negative_basepoint = basepoint;
406 negative_basepoint[31] |= 0x80;
407 let identity = {
408 let mut point = [0u8; CRYPTO_CORE_ED25519_BYTES];
409 point[0] = 1;
410 point
411 };
412 let noncanonical_identity = {
413 let mut point = [0xff; CRYPTO_CORE_ED25519_BYTES];
414 point[0] = 0xee;
415 point[31] = 0x7f;
416 point
417 };
418 let torsion = curve25519_dalek::constants::EIGHT_TORSION[1];
419 let mixed_order = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion)
420 .compress()
421 .to_bytes();
422
423 for point in [
424 basepoint,
425 negative_basepoint,
426 identity,
427 noncanonical_identity,
428 torsion.compress().to_bytes(),
429 mixed_order,
430 [0u8; CRYPTO_CORE_ED25519_BYTES],
431 ] {
432 let dryoc_result = crypto_core_ed25519_is_valid_point(&point);
433 let sodium_result = unsafe { sodium_is_valid_point(point.as_ptr()) } == 1;
434 assert_eq!(dryoc_result, sodium_result, "point: {point:02x?}");
435 }
436
437 for _ in 0..20 {
438 let (public_key, _) = crypto_sign_keypair();
439 let sodium_result = unsafe { sodium_is_valid_point(public_key.as_ptr()) } == 1;
440 assert!(sodium_result);
441 assert_eq!(
442 crypto_core_ed25519_is_valid_point(&public_key),
443 sodium_result
444 );
445 }
446 }
447
448 #[test]
449 fn test_crypto_scalarmult_base() {
450 use base64::Engine as _;
451 use base64::engine::general_purpose;
452 for _ in 0..20 {
453 use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
454
455 let (pk, sk) = crypto_box_keypair();
456
457 let mut public_key = [0u8; CRYPTO_SCALARMULT_BYTES];
458 crypto_scalarmult_base(&mut public_key, &sk);
459
460 assert_eq!(&pk, &public_key);
461
462 let ge = scalarmult_base(&Scalar::from_slice(&sk).unwrap());
463
464 assert_eq!(
465 general_purpose::STANDARD.encode(ge.as_ref()),
466 general_purpose::STANDARD.encode(public_key)
467 );
468 }
469 }
470
471 #[test]
472 fn test_crypto_scalarmult() {
473 use base64::Engine as _;
474 use base64::engine::general_purpose;
475 for _ in 0..20 {
476 use sodiumoxide::crypto::scalarmult::curve25519::{
477 GroupElement, Scalar, scalarmult,
478 };
479
480 let (_our_pk, our_sk) = crypto_box_keypair();
481 let (their_pk, _their_sk) = crypto_box_keypair();
482
483 let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
484 crypto_scalarmult(&mut shared_secret, &our_sk, &their_pk)
485 .expect("scalarmult failed");
486
487 let ge = scalarmult(
488 &Scalar::from_slice(&our_sk).unwrap(),
489 &GroupElement::from_slice(&their_pk).unwrap(),
490 )
491 .expect("scalarmult failed");
492
493 assert_eq!(
494 general_purpose::STANDARD.encode(ge.as_ref()),
495 general_purpose::STANDARD.encode(shared_secret)
496 );
497 }
498 }
499
500 #[test]
501 fn test_crypto_scalarmult_low_order_compatibility() {
502 use sodiumoxide::crypto::scalarmult::curve25519::{GroupElement, Scalar, scalarmult};
503
504 let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
505 let mut one = [0u8; CRYPTO_SCALARMULT_BYTES];
506 one[0] = 1;
507
508 for public_key in [[0u8; CRYPTO_SCALARMULT_BYTES], one] {
509 let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
510 assert!(crypto_scalarmult(&mut shared_secret, &scalar, &public_key).is_err());
511 assert!(
512 scalarmult(
513 &Scalar::from_slice(&scalar).unwrap(),
514 &GroupElement::from_slice(&public_key).unwrap(),
515 )
516 .is_err()
517 );
518 }
519 }
520
521 #[test]
522 fn test_crypto_core_hchacha20() {
523 use base64::Engine as _;
524 use base64::engine::general_purpose;
525 use libsodium_sys::crypto_core_hchacha20 as so_crypto_core_hchacha20;
526
527 use crate::rng::copy_randombytes;
528
529 for _ in 0..10 {
530 let mut key = [0u8; 32];
531 let mut data = [0u8; 16];
532 copy_randombytes(&mut key);
533 copy_randombytes(&mut data);
534
535 let mut out = [0u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
536 crypto_core_hchacha20(&mut out, &data, &key, None);
537
538 let mut so_out = [0u8; 32];
539 unsafe {
540 let ret = so_crypto_core_hchacha20(
541 so_out.as_mut_ptr(),
542 data.as_ptr(),
543 key.as_ptr(),
544 std::ptr::null(),
545 );
546 assert_eq!(ret, 0);
547 }
548 assert_eq!(
549 general_purpose::STANDARD.encode(out),
550 general_purpose::STANDARD.encode(so_out)
551 );
552 }
553 }
554
555 #[test]
556 fn test_crypto_core_hsalsa20() {
557 use base64::Engine as _;
558 use base64::engine::general_purpose;
559 use libsodium_sys::crypto_core_hsalsa20 as so_crypto_core_hsalsa20;
560
561 use crate::rng::copy_randombytes;
562
563 for _ in 0..10 {
564 let mut key = [0u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
565 let mut data = [0u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
566 copy_randombytes(&mut key);
567 copy_randombytes(&mut data);
568
569 let mut out = [0u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
570 crypto_core_hsalsa20(&mut out, &data, &key, None);
571
572 let mut so_out = [0u8; 32];
573 unsafe {
574 let ret = so_crypto_core_hsalsa20(
575 so_out.as_mut_ptr(),
576 data.as_ptr(),
577 key.as_ptr(),
578 std::ptr::null(),
579 );
580 assert_eq!(ret, 0);
581 }
582 assert_eq!(
583 general_purpose::STANDARD.encode(out),
584 general_purpose::STANDARD.encode(so_out)
585 );
586 }
587 }
588 }
589}