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) {
220 Ok(compressed) => compressed.decompress().is_some(),
221 Err(_) => false, }
223}
224
225#[inline]
226fn salsa20_rotl32(x: u32, y: u32, rot: u32) -> u32 {
227 x.wrapping_add(y).rotate_left(rot)
228}
229
230pub fn crypto_core_hsalsa20(
234 output: &mut HSalsa20Output,
235 input: &HSalsa20Input,
236 key: &HSalsa20Key,
237 constants: Option<(u32, u32, u32, u32)>,
238) {
239 let (mut x0, mut x5, mut x10, mut x15) =
240 constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
241 let (
242 mut x1,
243 mut x2,
244 mut x3,
245 mut x4,
246 mut x11,
247 mut x12,
248 mut x13,
249 mut x14,
250 mut x6,
251 mut x7,
252 mut x8,
253 mut x9,
254 ) = (
255 load_u32_le(&key[0..4]),
256 load_u32_le(&key[4..8]),
257 load_u32_le(&key[8..12]),
258 load_u32_le(&key[12..16]),
259 load_u32_le(&key[16..20]),
260 load_u32_le(&key[20..24]),
261 load_u32_le(&key[24..28]),
262 load_u32_le(&key[28..32]),
263 load_u32_le(&input[0..4]),
264 load_u32_le(&input[4..8]),
265 load_u32_le(&input[8..12]),
266 load_u32_le(&input[12..16]),
267 );
268
269 for _ in (0..20).step_by(2) {
270 x4 ^= salsa20_rotl32(x0, x12, 7);
271 x8 ^= salsa20_rotl32(x4, x0, 9);
272 x12 ^= salsa20_rotl32(x8, x4, 13);
273 x0 ^= salsa20_rotl32(x12, x8, 18);
274 x9 ^= salsa20_rotl32(x5, x1, 7);
275 x13 ^= salsa20_rotl32(x9, x5, 9);
276 x1 ^= salsa20_rotl32(x13, x9, 13);
277 x5 ^= salsa20_rotl32(x1, x13, 18);
278 x14 ^= salsa20_rotl32(x10, x6, 7);
279 x2 ^= salsa20_rotl32(x14, x10, 9);
280 x6 ^= salsa20_rotl32(x2, x14, 13);
281 x10 ^= salsa20_rotl32(x6, x2, 18);
282 x3 ^= salsa20_rotl32(x15, x11, 7);
283 x7 ^= salsa20_rotl32(x3, x15, 9);
284 x11 ^= salsa20_rotl32(x7, x3, 13);
285 x15 ^= salsa20_rotl32(x11, x7, 18);
286 x1 ^= salsa20_rotl32(x0, x3, 7);
287 x2 ^= salsa20_rotl32(x1, x0, 9);
288 x3 ^= salsa20_rotl32(x2, x1, 13);
289 x0 ^= salsa20_rotl32(x3, x2, 18);
290 x6 ^= salsa20_rotl32(x5, x4, 7);
291 x7 ^= salsa20_rotl32(x6, x5, 9);
292 x4 ^= salsa20_rotl32(x7, x6, 13);
293 x5 ^= salsa20_rotl32(x4, x7, 18);
294 x11 ^= salsa20_rotl32(x10, x9, 7);
295 x8 ^= salsa20_rotl32(x11, x10, 9);
296 x9 ^= salsa20_rotl32(x8, x11, 13);
297 x10 ^= salsa20_rotl32(x9, x8, 18);
298 x12 ^= salsa20_rotl32(x15, x14, 7);
299 x13 ^= salsa20_rotl32(x12, x15, 9);
300 x14 ^= salsa20_rotl32(x13, x12, 13);
301 x15 ^= salsa20_rotl32(x14, x13, 18);
302 }
303
304 output[0..4].copy_from_slice(&x0.to_le_bytes());
305 output[4..8].copy_from_slice(&x5.to_le_bytes());
306 output[8..12].copy_from_slice(&x10.to_le_bytes());
307 output[12..16].copy_from_slice(&x15.to_le_bytes());
308 output[16..20].copy_from_slice(&x6.to_le_bytes());
309 output[20..24].copy_from_slice(&x7.to_le_bytes());
310 output[24..28].copy_from_slice(&x8.to_le_bytes());
311 output[28..32].copy_from_slice(&x9.to_le_bytes());
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::classic::crypto_box::*;
318 use crate::classic::crypto_sign::crypto_sign_keypair;
319 use crate::keypair::{KeyPair, PublicKey, SecretKey};
320
321 #[test]
322 fn test_crypto_scalarmult_base() {
323 use base64::Engine as _;
324 use base64::engine::general_purpose;
325 for _ in 0..20 {
326 use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
327
328 let (pk, sk) = crypto_box_keypair();
329
330 let mut public_key = [0u8; CRYPTO_SCALARMULT_BYTES];
331 crypto_scalarmult_base(&mut public_key, &sk);
332
333 assert_eq!(&pk, &public_key);
334
335 let ge = scalarmult_base(&Scalar::from_slice(&sk).unwrap());
336
337 assert_eq!(
338 general_purpose::STANDARD.encode(ge.as_ref()),
339 general_purpose::STANDARD.encode(public_key)
340 );
341 }
342 }
343
344 #[test]
345 fn test_crypto_scalarmult() {
346 use base64::Engine as _;
347 use base64::engine::general_purpose;
348 for _ in 0..20 {
349 use sodiumoxide::crypto::scalarmult::curve25519::{GroupElement, Scalar, scalarmult};
350
351 let (_our_pk, our_sk) = crypto_box_keypair();
352 let (their_pk, _their_sk) = crypto_box_keypair();
353
354 let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
355 crypto_scalarmult(&mut shared_secret, &our_sk, &their_pk);
356
357 let ge = scalarmult(
358 &Scalar::from_slice(&our_sk).unwrap(),
359 &GroupElement::from_slice(&their_pk).unwrap(),
360 )
361 .expect("scalarmult failed");
362
363 assert_eq!(
364 general_purpose::STANDARD.encode(ge.as_ref()),
365 general_purpose::STANDARD.encode(shared_secret)
366 );
367 }
368 }
369
370 #[test]
371 fn test_crypto_core_hchacha20() {
372 use base64::Engine as _;
373 use base64::engine::general_purpose;
374 use libsodium_sys::crypto_core_hchacha20 as so_crypto_core_hchacha20;
375
376 use crate::rng::copy_randombytes;
377
378 for _ in 0..10 {
379 let mut key = [0u8; 32];
380 let mut data = [0u8; 16];
381 copy_randombytes(&mut key);
382 copy_randombytes(&mut data);
383
384 let mut out = [0u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
385 crypto_core_hchacha20(&mut out, &data, &key, None);
386
387 let mut so_out = [0u8; 32];
388 unsafe {
389 let ret = so_crypto_core_hchacha20(
390 so_out.as_mut_ptr(),
391 data.as_ptr(),
392 key.as_ptr(),
393 std::ptr::null(),
394 );
395 assert_eq!(ret, 0);
396 }
397 assert_eq!(
398 general_purpose::STANDARD.encode(out),
399 general_purpose::STANDARD.encode(so_out)
400 );
401 }
402 }
403
404 #[test]
405 fn test_crypto_core_hsalsa20() {
406 use base64::Engine as _;
407 use base64::engine::general_purpose;
408 use libsodium_sys::crypto_core_hsalsa20 as so_crypto_core_hsalsa20;
409
410 use crate::rng::copy_randombytes;
411
412 for _ in 0..10 {
413 let mut key = [0u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
414 let mut data = [0u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
415 copy_randombytes(&mut key);
416 copy_randombytes(&mut data);
417
418 let mut out = [0u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
419 crypto_core_hsalsa20(&mut out, &data, &key, None);
420
421 let mut so_out = [0u8; 32];
422 unsafe {
423 let ret = so_crypto_core_hsalsa20(
424 so_out.as_mut_ptr(),
425 data.as_ptr(),
426 key.as_ptr(),
427 std::ptr::null(),
428 );
429 assert_eq!(ret, 0);
430 }
431 assert_eq!(
432 general_purpose::STANDARD.encode(out),
433 general_purpose::STANDARD.encode(so_out)
434 );
435 }
436 }
437
438 #[test]
439 fn test_crypto_core_ed25519_is_valid_point() {
440 let valid_pk = [
443 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
444 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
445 ];
446 assert!(
447 crypto_core_ed25519_is_valid_point(&valid_pk),
448 "Known valid Ed25519 public key should be considered valid"
449 );
450
451 let mut invalid_point_high_bit = [0u8; CRYPTO_CORE_ED25519_BYTES];
454 invalid_point_high_bit[31] = 0x80; assert!(
456 !crypto_core_ed25519_is_valid_point(&invalid_point_high_bit),
457 "Point with high bit set in last byte should be invalid"
458 );
459
460 let small_order_point_identity = [
467 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,
468 0, 0, 0,
469 ];
470 assert!(
471 !crypto_core_ed25519_is_valid_point(&small_order_point_identity),
472 "Small-order point (identity element) should be rejected by stricter validation"
473 );
474
475 let mut point_not_on_curve = [0u8; CRYPTO_CORE_ED25519_BYTES];
479 point_not_on_curve[0] = 2; assert!(
483 !crypto_core_ed25519_is_valid_point(&point_not_on_curve),
484 "Point not on the curve should be invalid"
485 );
486
487 let zero_point = [0u8; CRYPTO_CORE_ED25519_BYTES];
489 assert!(
490 !crypto_core_ed25519_is_valid_point(&zero_point),
491 "Zero point represents invalid encoding"
492 );
493 }
494
495 #[test]
496 fn test_keypair_on_curve() {
497 let iterations = 25;
499 let mut strict_failures = 0;
500 let mut relaxed_failures = 0;
501
502 println!(
503 "\n=== Testing Ed25519 key validation across {} iterations ===",
504 iterations
505 );
506
507 for i in 0..iterations {
508 let (ed25519_pk, _) = crypto_sign_keypair();
510
511 let strict_valid = crypto_core_ed25519_is_valid_point(&ed25519_pk);
513
514 let relaxed_valid = crypto_core_ed25519_is_valid_point_relaxed(&ed25519_pk);
516
517 if !strict_valid {
518 strict_failures += 1;
519 let high_bit_set = ed25519_pk[CRYPTO_CORE_ED25519_BYTES - 1] & 0x80 != 0;
521 println!("Iteration {}: Ed25519 key strict validation failed:", i);
522 println!(" High bit set: {}", high_bit_set);
523 }
524
525 if !relaxed_valid {
526 relaxed_failures += 1;
527 println!(
529 "ERROR: Iteration {}: Ed25519 key failed relaxed validation",
530 i
531 );
532
533 const ZERO_POINT: Ed25519Point = [0u8; CRYPTO_CORE_ED25519_BYTES];
535 let is_zero = ed25519_pk == ZERO_POINT;
536
537 const SMALL_ORDER_POINT_IDENTITY: Ed25519Point = [
538 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,
539 0, 0, 0, 0, 0, 0,
540 ];
541 let is_identity = ed25519_pk == SMALL_ORDER_POINT_IDENTITY;
542
543 let on_curve = match CompressedEdwardsY::from_slice(&ed25519_pk) {
544 Ok(compressed) => compressed.decompress().is_some(),
545 Err(_) => false,
546 };
547
548 println!(" Zero point: {}", is_zero);
549 println!(" Identity element: {}", is_identity);
550 println!(" On curve: {}", on_curve);
551 println!(" Key: {:?}", ed25519_pk);
552 }
553
554 assert!(
556 relaxed_valid,
557 "Generated Ed25519 key failed relaxed validation"
558 );
559 }
560
561 println!(
562 "\nSummary: {} of {} Ed25519 keys failed strict validation",
563 strict_failures, iterations
564 );
565 println!(
566 "Summary: {} of {} Ed25519 keys failed relaxed validation",
567 relaxed_failures, iterations
568 );
569
570 println!("\n=== Testing X25519 key validation ===");
573 let (x25519_pk, _) = crypto_box_keypair();
574
575 assert!(
576 KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&x25519_pk),
577 "X25519 public key should be valid according to X25519 rules"
578 );
579
580 println!("X25519 key validation: Success");
581 }
582}