1use curve25519_dalek::edwards::CompressedEdwardsY;
2
3use crate::constants::{
4 CRYPTO_CORE_ED25519_BYTES, CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_CORE_HCHACHA20_KEYBYTES,
5 CRYPTO_CORE_HCHACHA20_OUTPUTBYTES, CRYPTO_CORE_HSALSA20_INPUTBYTES,
6 CRYPTO_CORE_HSALSA20_KEYBYTES, CRYPTO_CORE_HSALSA20_OUTPUTBYTES, CRYPTO_SCALARMULT_BYTES,
7 CRYPTO_SCALARMULT_SCALARBYTES,
8};
9use crate::scalarmult_curve25519::{
10 crypto_scalarmult_curve25519, crypto_scalarmult_curve25519_base,
11};
12use crate::types::*;
13use crate::utils::load_u32_le;
14
15pub type HChaCha20Input = [u8; CRYPTO_CORE_HCHACHA20_INPUTBYTES];
17pub type HChaCha20Key = [u8; CRYPTO_CORE_HCHACHA20_KEYBYTES];
19pub type HChaCha20Output = [u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
21pub type HSalsa20Input = [u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
23pub type HSalsa20Key = [u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
25pub type HSalsa20Output = [u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
27pub type Ed25519Point = [u8; CRYPTO_CORE_ED25519_BYTES];
29
30pub fn crypto_scalarmult_base(
34 q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
35 n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
36) {
37 crypto_scalarmult_curve25519_base(q, n)
38}
39
40pub fn crypto_scalarmult(
45 q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
46 n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
47 p: &[u8; CRYPTO_SCALARMULT_BYTES],
48) {
49 crypto_scalarmult_curve25519(q, n, p)
50}
51
52#[inline]
53fn chacha20_round(x: &mut u32, y: &u32, z: &mut u32, rot: u32) {
54 *x = x.wrapping_add(*y);
55 *z = (*z ^ *x).rotate_left(rot);
56}
57
58#[inline]
59fn chacha20_quarterround(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
60 chacha20_round(a, b, d, 16);
61 chacha20_round(c, d, b, 12);
62 chacha20_round(a, b, d, 8);
63 chacha20_round(c, d, b, 7);
64}
65
66pub fn crypto_core_hchacha20(
70 output: &mut HChaCha20Output,
71 input: &HChaCha20Input,
72 key: &HChaCha20Key,
73 constants: Option<(u32, u32, u32, u32)>,
74) {
75 let input = input.as_array();
76 let key = key.as_array();
77 assert_eq!(input.len(), 16);
78 assert_eq!(key.len(), 32);
79 let (mut x0, mut x1, mut x2, mut x3) =
80 constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
81 let (
82 mut x4,
83 mut x5,
84 mut x6,
85 mut x7,
86 mut x8,
87 mut x9,
88 mut x10,
89 mut x11,
90 mut x12,
91 mut x13,
92 mut x14,
93 mut x15,
94 ) = (
95 load_u32_le(&key[0..4]),
96 load_u32_le(&key[4..8]),
97 load_u32_le(&key[8..12]),
98 load_u32_le(&key[12..16]),
99 load_u32_le(&key[16..20]),
100 load_u32_le(&key[20..24]),
101 load_u32_le(&key[24..28]),
102 load_u32_le(&key[28..32]),
103 load_u32_le(&input[0..4]),
104 load_u32_le(&input[4..8]),
105 load_u32_le(&input[8..12]),
106 load_u32_le(&input[12..16]),
107 );
108
109 for _ in 0..10 {
110 chacha20_quarterround(&mut x0, &mut x4, &mut x8, &mut x12);
111 chacha20_quarterround(&mut x1, &mut x5, &mut x9, &mut x13);
112 chacha20_quarterround(&mut x2, &mut x6, &mut x10, &mut x14);
113 chacha20_quarterround(&mut x3, &mut x7, &mut x11, &mut x15);
114 chacha20_quarterround(&mut x0, &mut x5, &mut x10, &mut x15);
115 chacha20_quarterround(&mut x1, &mut x6, &mut x11, &mut x12);
116 chacha20_quarterround(&mut x2, &mut x7, &mut x8, &mut x13);
117 chacha20_quarterround(&mut x3, &mut x4, &mut x9, &mut x14);
118 }
119
120 output[0..4].copy_from_slice(&x0.to_le_bytes());
121 output[4..8].copy_from_slice(&x1.to_le_bytes());
122 output[8..12].copy_from_slice(&x2.to_le_bytes());
123 output[12..16].copy_from_slice(&x3.to_le_bytes());
124 output[16..20].copy_from_slice(&x12.to_le_bytes());
125 output[20..24].copy_from_slice(&x13.to_le_bytes());
126 output[24..28].copy_from_slice(&x14.to_le_bytes());
127 output[28..32].copy_from_slice(&x15.to_le_bytes());
128}
129
130pub fn crypto_core_ed25519_is_valid_point(p: &Ed25519Point) -> bool {
176 crypto_core_ed25519_is_valid_point_internal(p, false)
177}
178
179pub fn crypto_core_ed25519_is_valid_point_relaxed(p: &Ed25519Point) -> bool {
186 crypto_core_ed25519_is_valid_point_internal(p, true)
187}
188
189fn crypto_core_ed25519_is_valid_point_internal(p: &Ed25519Point, ignore_high_bit: bool) -> bool {
192 let last_byte = p[CRYPTO_CORE_ED25519_BYTES - 1];
195 if !ignore_high_bit && last_byte & 0x80 != 0 {
196 return false;
197 }
198
199 const ZERO_POINT: Ed25519Point = [0u8; CRYPTO_CORE_ED25519_BYTES];
201 if p == &ZERO_POINT {
202 return false;
203 }
204
205 const SMALL_ORDER_POINT_IDENTITY: Ed25519Point = [
208 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
209 0, 0,
210 ];
211 if p == &SMALL_ORDER_POINT_IDENTITY {
212 return false;
213 }
214
215 match CompressedEdwardsY::from_slice(p) {
218 Ok(compressed) => match compressed.decompress() {
219 Some(point) => point.is_torsion_free(),
220 None => false,
221 },
222 Err(_) => false, }
224}
225
226#[inline]
227fn salsa20_rotl32(x: u32, y: u32, rot: u32) -> u32 {
228 x.wrapping_add(y).rotate_left(rot)
229}
230
231pub fn crypto_core_hsalsa20(
235 output: &mut HSalsa20Output,
236 input: &HSalsa20Input,
237 key: &HSalsa20Key,
238 constants: Option<(u32, u32, u32, u32)>,
239) {
240 let (mut x0, mut x5, mut x10, mut x15) =
241 constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
242 let (
243 mut x1,
244 mut x2,
245 mut x3,
246 mut x4,
247 mut x11,
248 mut x12,
249 mut x13,
250 mut x14,
251 mut x6,
252 mut x7,
253 mut x8,
254 mut x9,
255 ) = (
256 load_u32_le(&key[0..4]),
257 load_u32_le(&key[4..8]),
258 load_u32_le(&key[8..12]),
259 load_u32_le(&key[12..16]),
260 load_u32_le(&key[16..20]),
261 load_u32_le(&key[20..24]),
262 load_u32_le(&key[24..28]),
263 load_u32_le(&key[28..32]),
264 load_u32_le(&input[0..4]),
265 load_u32_le(&input[4..8]),
266 load_u32_le(&input[8..12]),
267 load_u32_le(&input[12..16]),
268 );
269
270 for _ in (0..20).step_by(2) {
271 x4 ^= salsa20_rotl32(x0, x12, 7);
272 x8 ^= salsa20_rotl32(x4, x0, 9);
273 x12 ^= salsa20_rotl32(x8, x4, 13);
274 x0 ^= salsa20_rotl32(x12, x8, 18);
275 x9 ^= salsa20_rotl32(x5, x1, 7);
276 x13 ^= salsa20_rotl32(x9, x5, 9);
277 x1 ^= salsa20_rotl32(x13, x9, 13);
278 x5 ^= salsa20_rotl32(x1, x13, 18);
279 x14 ^= salsa20_rotl32(x10, x6, 7);
280 x2 ^= salsa20_rotl32(x14, x10, 9);
281 x6 ^= salsa20_rotl32(x2, x14, 13);
282 x10 ^= salsa20_rotl32(x6, x2, 18);
283 x3 ^= salsa20_rotl32(x15, x11, 7);
284 x7 ^= salsa20_rotl32(x3, x15, 9);
285 x11 ^= salsa20_rotl32(x7, x3, 13);
286 x15 ^= salsa20_rotl32(x11, x7, 18);
287 x1 ^= salsa20_rotl32(x0, x3, 7);
288 x2 ^= salsa20_rotl32(x1, x0, 9);
289 x3 ^= salsa20_rotl32(x2, x1, 13);
290 x0 ^= salsa20_rotl32(x3, x2, 18);
291 x6 ^= salsa20_rotl32(x5, x4, 7);
292 x7 ^= salsa20_rotl32(x6, x5, 9);
293 x4 ^= salsa20_rotl32(x7, x6, 13);
294 x5 ^= salsa20_rotl32(x4, x7, 18);
295 x11 ^= salsa20_rotl32(x10, x9, 7);
296 x8 ^= salsa20_rotl32(x11, x10, 9);
297 x9 ^= salsa20_rotl32(x8, x11, 13);
298 x10 ^= salsa20_rotl32(x9, x8, 18);
299 x12 ^= salsa20_rotl32(x15, x14, 7);
300 x13 ^= salsa20_rotl32(x12, x15, 9);
301 x14 ^= salsa20_rotl32(x13, x12, 13);
302 x15 ^= salsa20_rotl32(x14, x13, 18);
303 }
304
305 output[0..4].copy_from_slice(&x0.to_le_bytes());
306 output[4..8].copy_from_slice(&x5.to_le_bytes());
307 output[8..12].copy_from_slice(&x10.to_le_bytes());
308 output[12..16].copy_from_slice(&x15.to_le_bytes());
309 output[16..20].copy_from_slice(&x6.to_le_bytes());
310 output[20..24].copy_from_slice(&x7.to_le_bytes());
311 output[24..28].copy_from_slice(&x8.to_le_bytes());
312 output[28..32].copy_from_slice(&x9.to_le_bytes());
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::classic::crypto_box::*;
319 use crate::classic::crypto_sign::crypto_sign_keypair;
320 use crate::keypair::{KeyPair, PublicKey, SecretKey};
321
322 #[test]
323 fn test_crypto_scalarmult_base() {
324 use base64::Engine as _;
325 use base64::engine::general_purpose;
326 for _ in 0..20 {
327 use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
328
329 let (pk, sk) = crypto_box_keypair();
330
331 let mut public_key = [0u8; CRYPTO_SCALARMULT_BYTES];
332 crypto_scalarmult_base(&mut public_key, &sk);
333
334 assert_eq!(&pk, &public_key);
335
336 let ge = scalarmult_base(&Scalar::from_slice(&sk).unwrap());
337
338 assert_eq!(
339 general_purpose::STANDARD.encode(ge.as_ref()),
340 general_purpose::STANDARD.encode(public_key)
341 );
342 }
343 }
344
345 #[test]
346 fn test_crypto_scalarmult() {
347 use base64::Engine as _;
348 use base64::engine::general_purpose;
349 for _ in 0..20 {
350 use sodiumoxide::crypto::scalarmult::curve25519::{GroupElement, Scalar, scalarmult};
351
352 let (_our_pk, our_sk) = crypto_box_keypair();
353 let (their_pk, _their_sk) = crypto_box_keypair();
354
355 let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
356 crypto_scalarmult(&mut shared_secret, &our_sk, &their_pk);
357
358 let ge = scalarmult(
359 &Scalar::from_slice(&our_sk).unwrap(),
360 &GroupElement::from_slice(&their_pk).unwrap(),
361 )
362 .expect("scalarmult failed");
363
364 assert_eq!(
365 general_purpose::STANDARD.encode(ge.as_ref()),
366 general_purpose::STANDARD.encode(shared_secret)
367 );
368 }
369 }
370
371 #[test]
372 fn test_crypto_core_hchacha20() {
373 use base64::Engine as _;
374 use base64::engine::general_purpose;
375 use libsodium_sys::crypto_core_hchacha20 as so_crypto_core_hchacha20;
376
377 use crate::rng::copy_randombytes;
378
379 for _ in 0..10 {
380 let mut key = [0u8; 32];
381 let mut data = [0u8; 16];
382 copy_randombytes(&mut key);
383 copy_randombytes(&mut data);
384
385 let mut out = [0u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
386 crypto_core_hchacha20(&mut out, &data, &key, None);
387
388 let mut so_out = [0u8; 32];
389 unsafe {
390 let ret = so_crypto_core_hchacha20(
391 so_out.as_mut_ptr(),
392 data.as_ptr(),
393 key.as_ptr(),
394 std::ptr::null(),
395 );
396 assert_eq!(ret, 0);
397 }
398 assert_eq!(
399 general_purpose::STANDARD.encode(out),
400 general_purpose::STANDARD.encode(so_out)
401 );
402 }
403 }
404
405 #[test]
406 fn test_crypto_core_hsalsa20() {
407 use base64::Engine as _;
408 use base64::engine::general_purpose;
409 use libsodium_sys::crypto_core_hsalsa20 as so_crypto_core_hsalsa20;
410
411 use crate::rng::copy_randombytes;
412
413 for _ in 0..10 {
414 let mut key = [0u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
415 let mut data = [0u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
416 copy_randombytes(&mut key);
417 copy_randombytes(&mut data);
418
419 let mut out = [0u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
420 crypto_core_hsalsa20(&mut out, &data, &key, None);
421
422 let mut so_out = [0u8; 32];
423 unsafe {
424 let ret = so_crypto_core_hsalsa20(
425 so_out.as_mut_ptr(),
426 data.as_ptr(),
427 key.as_ptr(),
428 std::ptr::null(),
429 );
430 assert_eq!(ret, 0);
431 }
432 assert_eq!(
433 general_purpose::STANDARD.encode(out),
434 general_purpose::STANDARD.encode(so_out)
435 );
436 }
437 }
438
439 #[test]
440 fn test_crypto_core_ed25519_is_valid_point() {
441 let valid_pk = [
444 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
445 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
446 ];
447 assert!(
448 crypto_core_ed25519_is_valid_point(&valid_pk),
449 "Known valid Ed25519 public key should be considered valid"
450 );
451
452 let mut invalid_point_high_bit = [0u8; CRYPTO_CORE_ED25519_BYTES];
455 invalid_point_high_bit[31] = 0x80; assert!(
457 !crypto_core_ed25519_is_valid_point(&invalid_point_high_bit),
458 "Point with high bit set in last byte should be invalid"
459 );
460
461 let small_order_point_identity = [
468 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
469 0, 0, 0,
470 ];
471 assert!(
472 !crypto_core_ed25519_is_valid_point(&small_order_point_identity),
473 "Small-order point (identity element) should be rejected by stricter validation"
474 );
475
476 use curve25519_dalek::traits::IsIdentity;
479
480 let torsion_point = curve25519_dalek::constants::EIGHT_TORSION
481 .iter()
482 .find(|point| {
483 let torsion_bytes = point.compress().to_bytes();
484 let mixed_point = curve25519_dalek::constants::ED25519_BASEPOINT_POINT + *point;
485 let mixed_bytes = mixed_point.compress().to_bytes();
486 torsion_bytes[31] & 0x80 == 0
487 && mixed_bytes[31] & 0x80 == 0
488 && !point.is_identity()
489 && !mixed_point.is_torsion_free()
490 })
491 .copied()
492 .expect("expected a non-identity torsion point with canonical encoding");
493
494 let torsion_bytes = torsion_point.compress().to_bytes();
495 assert!(
496 !crypto_core_ed25519_is_valid_point(&torsion_bytes),
497 "Torsion point should be rejected"
498 );
499 assert!(
500 !crypto_core_ed25519_is_valid_point_relaxed(&torsion_bytes),
501 "Torsion point should be rejected even with relaxed validation"
502 );
503
504 let mixed_bytes = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion_point)
505 .compress()
506 .to_bytes();
507 assert!(
508 !crypto_core_ed25519_is_valid_point(&mixed_bytes),
509 "Mixed-order point should be rejected"
510 );
511 assert!(
512 !crypto_core_ed25519_is_valid_point_relaxed(&mixed_bytes),
513 "Mixed-order point should be rejected even with relaxed validation"
514 );
515
516 let mut point_not_on_curve = [0u8; CRYPTO_CORE_ED25519_BYTES];
520 point_not_on_curve[0] = 2; assert!(
524 !crypto_core_ed25519_is_valid_point(&point_not_on_curve),
525 "Point not on the curve should be invalid"
526 );
527
528 let zero_point = [0u8; CRYPTO_CORE_ED25519_BYTES];
530 assert!(
531 !crypto_core_ed25519_is_valid_point(&zero_point),
532 "Zero point represents invalid encoding"
533 );
534 }
535
536 #[test]
537 fn test_keypair_on_curve() {
538 let iterations = 25;
540 let mut strict_failures = 0;
541 let mut relaxed_failures = 0;
542
543 println!(
544 "\n=== Testing Ed25519 key validation across {} iterations ===",
545 iterations
546 );
547
548 for i in 0..iterations {
549 let (ed25519_pk, _) = crypto_sign_keypair();
551
552 let strict_valid = crypto_core_ed25519_is_valid_point(&ed25519_pk);
554
555 let relaxed_valid = crypto_core_ed25519_is_valid_point_relaxed(&ed25519_pk);
557
558 if !strict_valid {
559 strict_failures += 1;
560 let high_bit_set = ed25519_pk[CRYPTO_CORE_ED25519_BYTES - 1] & 0x80 != 0;
562 println!("Iteration {}: Ed25519 key strict validation failed:", i);
563 println!(" High bit set: {}", high_bit_set);
564 }
565
566 if !relaxed_valid {
567 relaxed_failures += 1;
568 println!(
570 "ERROR: Iteration {}: Ed25519 key failed relaxed validation",
571 i
572 );
573
574 const ZERO_POINT: Ed25519Point = [0u8; CRYPTO_CORE_ED25519_BYTES];
576 let is_zero = ed25519_pk == ZERO_POINT;
577
578 const SMALL_ORDER_POINT_IDENTITY: Ed25519Point = [
579 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
580 0, 0, 0, 0, 0, 0,
581 ];
582 let is_identity = ed25519_pk == SMALL_ORDER_POINT_IDENTITY;
583
584 let on_curve = match CompressedEdwardsY::from_slice(&ed25519_pk) {
585 Ok(compressed) => compressed.decompress().is_some(),
586 Err(_) => false,
587 };
588
589 println!(" Zero point: {}", is_zero);
590 println!(" Identity element: {}", is_identity);
591 println!(" On curve: {}", on_curve);
592 println!(" Key: {:?}", ed25519_pk);
593 }
594
595 assert!(
597 relaxed_valid,
598 "Generated Ed25519 key failed relaxed validation"
599 );
600 }
601
602 println!(
603 "\nSummary: {} of {} Ed25519 keys failed strict validation",
604 strict_failures, iterations
605 );
606 println!(
607 "Summary: {} of {} Ed25519 keys failed relaxed validation",
608 relaxed_failures, iterations
609 );
610
611 println!("\n=== Testing X25519 key validation ===");
614 let (x25519_pk, _) = crypto_box_keypair();
615
616 assert!(
617 KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&x25519_pk),
618 "X25519 public key should be valid according to X25519 rules"
619 );
620
621 println!("X25519 key validation: Success");
622 }
623}