use curve25519_dalek::edwards::CompressedEdwardsY;
use crate::constants::{
CRYPTO_CORE_ED25519_BYTES, CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_CORE_HCHACHA20_KEYBYTES,
CRYPTO_CORE_HCHACHA20_OUTPUTBYTES, CRYPTO_CORE_HSALSA20_INPUTBYTES,
CRYPTO_CORE_HSALSA20_KEYBYTES, CRYPTO_CORE_HSALSA20_OUTPUTBYTES, CRYPTO_SCALARMULT_BYTES,
CRYPTO_SCALARMULT_SCALARBYTES,
};
use crate::scalarmult_curve25519::{
crypto_scalarmult_curve25519, crypto_scalarmult_curve25519_base,
};
use crate::types::*;
use crate::utils::load_u32_le;
pub type HChaCha20Input = [u8; CRYPTO_CORE_HCHACHA20_INPUTBYTES];
pub type HChaCha20Key = [u8; CRYPTO_CORE_HCHACHA20_KEYBYTES];
pub type HChaCha20Output = [u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
pub type HSalsa20Input = [u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
pub type HSalsa20Key = [u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
pub type HSalsa20Output = [u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
pub type Ed25519Point = [u8; CRYPTO_CORE_ED25519_BYTES];
pub fn crypto_scalarmult_base(
q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
) {
crypto_scalarmult_curve25519_base(q, n)
}
pub fn crypto_scalarmult(
q: &mut [u8; CRYPTO_SCALARMULT_BYTES],
n: &[u8; CRYPTO_SCALARMULT_SCALARBYTES],
p: &[u8; CRYPTO_SCALARMULT_BYTES],
) {
crypto_scalarmult_curve25519(q, n, p)
}
#[inline]
fn chacha20_round(x: &mut u32, y: &u32, z: &mut u32, rot: u32) {
*x = x.wrapping_add(*y);
*z = (*z ^ *x).rotate_left(rot);
}
#[inline]
fn chacha20_quarterround(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
chacha20_round(a, b, d, 16);
chacha20_round(c, d, b, 12);
chacha20_round(a, b, d, 8);
chacha20_round(c, d, b, 7);
}
pub fn crypto_core_hchacha20(
output: &mut HChaCha20Output,
input: &HChaCha20Input,
key: &HChaCha20Key,
constants: Option<(u32, u32, u32, u32)>,
) {
let input = input.as_array();
let key = key.as_array();
assert_eq!(input.len(), 16);
assert_eq!(key.len(), 32);
let (mut x0, mut x1, mut x2, mut x3) =
constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
let (
mut x4,
mut x5,
mut x6,
mut x7,
mut x8,
mut x9,
mut x10,
mut x11,
mut x12,
mut x13,
mut x14,
mut x15,
) = (
load_u32_le(&key[0..4]),
load_u32_le(&key[4..8]),
load_u32_le(&key[8..12]),
load_u32_le(&key[12..16]),
load_u32_le(&key[16..20]),
load_u32_le(&key[20..24]),
load_u32_le(&key[24..28]),
load_u32_le(&key[28..32]),
load_u32_le(&input[0..4]),
load_u32_le(&input[4..8]),
load_u32_le(&input[8..12]),
load_u32_le(&input[12..16]),
);
for _ in 0..10 {
chacha20_quarterround(&mut x0, &mut x4, &mut x8, &mut x12);
chacha20_quarterround(&mut x1, &mut x5, &mut x9, &mut x13);
chacha20_quarterround(&mut x2, &mut x6, &mut x10, &mut x14);
chacha20_quarterround(&mut x3, &mut x7, &mut x11, &mut x15);
chacha20_quarterround(&mut x0, &mut x5, &mut x10, &mut x15);
chacha20_quarterround(&mut x1, &mut x6, &mut x11, &mut x12);
chacha20_quarterround(&mut x2, &mut x7, &mut x8, &mut x13);
chacha20_quarterround(&mut x3, &mut x4, &mut x9, &mut x14);
}
output[0..4].copy_from_slice(&x0.to_le_bytes());
output[4..8].copy_from_slice(&x1.to_le_bytes());
output[8..12].copy_from_slice(&x2.to_le_bytes());
output[12..16].copy_from_slice(&x3.to_le_bytes());
output[16..20].copy_from_slice(&x12.to_le_bytes());
output[20..24].copy_from_slice(&x13.to_le_bytes());
output[24..28].copy_from_slice(&x14.to_le_bytes());
output[28..32].copy_from_slice(&x15.to_le_bytes());
}
pub fn crypto_core_ed25519_is_valid_point(p: &Ed25519Point) -> bool {
crypto_core_ed25519_is_valid_point_internal(p, false)
}
pub fn crypto_core_ed25519_is_valid_point_relaxed(p: &Ed25519Point) -> bool {
crypto_core_ed25519_is_valid_point_internal(p, true)
}
fn crypto_core_ed25519_is_valid_point_internal(p: &Ed25519Point, ignore_high_bit: bool) -> bool {
let last_byte = p[CRYPTO_CORE_ED25519_BYTES - 1];
if !ignore_high_bit && last_byte & 0x80 != 0 {
return false;
}
const ZERO_POINT: Ed25519Point = [0u8; CRYPTO_CORE_ED25519_BYTES];
if p == &ZERO_POINT {
return false;
}
const SMALL_ORDER_POINT_IDENTITY: Ed25519Point = [
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,
0, 0,
];
if p == &SMALL_ORDER_POINT_IDENTITY {
return false;
}
match CompressedEdwardsY::from_slice(p) {
Ok(compressed) => match compressed.decompress() {
Some(point) => point.is_torsion_free(),
None => false,
},
Err(_) => false, }
}
#[inline]
fn salsa20_rotl32(x: u32, y: u32, rot: u32) -> u32 {
x.wrapping_add(y).rotate_left(rot)
}
pub fn crypto_core_hsalsa20(
output: &mut HSalsa20Output,
input: &HSalsa20Input,
key: &HSalsa20Key,
constants: Option<(u32, u32, u32, u32)>,
) {
let (mut x0, mut x5, mut x10, mut x15) =
constants.unwrap_or((0x61707865, 0x3320646e, 0x79622d32, 0x6b206574));
let (
mut x1,
mut x2,
mut x3,
mut x4,
mut x11,
mut x12,
mut x13,
mut x14,
mut x6,
mut x7,
mut x8,
mut x9,
) = (
load_u32_le(&key[0..4]),
load_u32_le(&key[4..8]),
load_u32_le(&key[8..12]),
load_u32_le(&key[12..16]),
load_u32_le(&key[16..20]),
load_u32_le(&key[20..24]),
load_u32_le(&key[24..28]),
load_u32_le(&key[28..32]),
load_u32_le(&input[0..4]),
load_u32_le(&input[4..8]),
load_u32_le(&input[8..12]),
load_u32_le(&input[12..16]),
);
for _ in (0..20).step_by(2) {
x4 ^= salsa20_rotl32(x0, x12, 7);
x8 ^= salsa20_rotl32(x4, x0, 9);
x12 ^= salsa20_rotl32(x8, x4, 13);
x0 ^= salsa20_rotl32(x12, x8, 18);
x9 ^= salsa20_rotl32(x5, x1, 7);
x13 ^= salsa20_rotl32(x9, x5, 9);
x1 ^= salsa20_rotl32(x13, x9, 13);
x5 ^= salsa20_rotl32(x1, x13, 18);
x14 ^= salsa20_rotl32(x10, x6, 7);
x2 ^= salsa20_rotl32(x14, x10, 9);
x6 ^= salsa20_rotl32(x2, x14, 13);
x10 ^= salsa20_rotl32(x6, x2, 18);
x3 ^= salsa20_rotl32(x15, x11, 7);
x7 ^= salsa20_rotl32(x3, x15, 9);
x11 ^= salsa20_rotl32(x7, x3, 13);
x15 ^= salsa20_rotl32(x11, x7, 18);
x1 ^= salsa20_rotl32(x0, x3, 7);
x2 ^= salsa20_rotl32(x1, x0, 9);
x3 ^= salsa20_rotl32(x2, x1, 13);
x0 ^= salsa20_rotl32(x3, x2, 18);
x6 ^= salsa20_rotl32(x5, x4, 7);
x7 ^= salsa20_rotl32(x6, x5, 9);
x4 ^= salsa20_rotl32(x7, x6, 13);
x5 ^= salsa20_rotl32(x4, x7, 18);
x11 ^= salsa20_rotl32(x10, x9, 7);
x8 ^= salsa20_rotl32(x11, x10, 9);
x9 ^= salsa20_rotl32(x8, x11, 13);
x10 ^= salsa20_rotl32(x9, x8, 18);
x12 ^= salsa20_rotl32(x15, x14, 7);
x13 ^= salsa20_rotl32(x12, x15, 9);
x14 ^= salsa20_rotl32(x13, x12, 13);
x15 ^= salsa20_rotl32(x14, x13, 18);
}
output[0..4].copy_from_slice(&x0.to_le_bytes());
output[4..8].copy_from_slice(&x5.to_le_bytes());
output[8..12].copy_from_slice(&x10.to_le_bytes());
output[12..16].copy_from_slice(&x15.to_le_bytes());
output[16..20].copy_from_slice(&x6.to_le_bytes());
output[20..24].copy_from_slice(&x7.to_le_bytes());
output[24..28].copy_from_slice(&x8.to_le_bytes());
output[28..32].copy_from_slice(&x9.to_le_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::classic::crypto_box::*;
use crate::classic::crypto_sign::crypto_sign_keypair;
use crate::keypair::{KeyPair, PublicKey, SecretKey};
#[test]
fn test_crypto_scalarmult_base() {
use base64::Engine as _;
use base64::engine::general_purpose;
for _ in 0..20 {
use sodiumoxide::crypto::scalarmult::curve25519::{Scalar, scalarmult_base};
let (pk, sk) = crypto_box_keypair();
let mut public_key = [0u8; CRYPTO_SCALARMULT_BYTES];
crypto_scalarmult_base(&mut public_key, &sk);
assert_eq!(&pk, &public_key);
let ge = scalarmult_base(&Scalar::from_slice(&sk).unwrap());
assert_eq!(
general_purpose::STANDARD.encode(ge.as_ref()),
general_purpose::STANDARD.encode(public_key)
);
}
}
#[test]
fn test_crypto_scalarmult() {
use base64::Engine as _;
use base64::engine::general_purpose;
for _ in 0..20 {
use sodiumoxide::crypto::scalarmult::curve25519::{GroupElement, Scalar, scalarmult};
let (_our_pk, our_sk) = crypto_box_keypair();
let (their_pk, _their_sk) = crypto_box_keypair();
let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
crypto_scalarmult(&mut shared_secret, &our_sk, &their_pk);
let ge = scalarmult(
&Scalar::from_slice(&our_sk).unwrap(),
&GroupElement::from_slice(&their_pk).unwrap(),
)
.expect("scalarmult failed");
assert_eq!(
general_purpose::STANDARD.encode(ge.as_ref()),
general_purpose::STANDARD.encode(shared_secret)
);
}
}
#[test]
fn test_crypto_core_hchacha20() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libsodium_sys::crypto_core_hchacha20 as so_crypto_core_hchacha20;
use crate::rng::copy_randombytes;
for _ in 0..10 {
let mut key = [0u8; 32];
let mut data = [0u8; 16];
copy_randombytes(&mut key);
copy_randombytes(&mut data);
let mut out = [0u8; CRYPTO_CORE_HCHACHA20_OUTPUTBYTES];
crypto_core_hchacha20(&mut out, &data, &key, None);
let mut so_out = [0u8; 32];
unsafe {
let ret = so_crypto_core_hchacha20(
so_out.as_mut_ptr(),
data.as_ptr(),
key.as_ptr(),
std::ptr::null(),
);
assert_eq!(ret, 0);
}
assert_eq!(
general_purpose::STANDARD.encode(out),
general_purpose::STANDARD.encode(so_out)
);
}
}
#[test]
fn test_crypto_core_hsalsa20() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libsodium_sys::crypto_core_hsalsa20 as so_crypto_core_hsalsa20;
use crate::rng::copy_randombytes;
for _ in 0..10 {
let mut key = [0u8; CRYPTO_CORE_HSALSA20_KEYBYTES];
let mut data = [0u8; CRYPTO_CORE_HSALSA20_INPUTBYTES];
copy_randombytes(&mut key);
copy_randombytes(&mut data);
let mut out = [0u8; CRYPTO_CORE_HSALSA20_OUTPUTBYTES];
crypto_core_hsalsa20(&mut out, &data, &key, None);
let mut so_out = [0u8; 32];
unsafe {
let ret = so_crypto_core_hsalsa20(
so_out.as_mut_ptr(),
data.as_ptr(),
key.as_ptr(),
std::ptr::null(),
);
assert_eq!(ret, 0);
}
assert_eq!(
general_purpose::STANDARD.encode(out),
general_purpose::STANDARD.encode(so_out)
);
}
}
#[test]
fn test_crypto_core_ed25519_is_valid_point() {
let valid_pk = [
215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114,
243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26,
];
assert!(
crypto_core_ed25519_is_valid_point(&valid_pk),
"Known valid Ed25519 public key should be considered valid"
);
let mut invalid_point_high_bit = [0u8; CRYPTO_CORE_ED25519_BYTES];
invalid_point_high_bit[31] = 0x80; assert!(
!crypto_core_ed25519_is_valid_point(&invalid_point_high_bit),
"Point with high bit set in last byte should be invalid"
);
let small_order_point_identity = [
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, 0, 0,
];
assert!(
!crypto_core_ed25519_is_valid_point(&small_order_point_identity),
"Small-order point (identity element) should be rejected by stricter validation"
);
use curve25519_dalek::traits::IsIdentity;
let torsion_point = curve25519_dalek::constants::EIGHT_TORSION
.iter()
.find(|point| {
let torsion_bytes = point.compress().to_bytes();
let mixed_point = curve25519_dalek::constants::ED25519_BASEPOINT_POINT + *point;
let mixed_bytes = mixed_point.compress().to_bytes();
torsion_bytes[31] & 0x80 == 0
&& mixed_bytes[31] & 0x80 == 0
&& !point.is_identity()
&& !mixed_point.is_torsion_free()
})
.copied()
.expect("expected a non-identity torsion point with canonical encoding");
let torsion_bytes = torsion_point.compress().to_bytes();
assert!(
!crypto_core_ed25519_is_valid_point(&torsion_bytes),
"Torsion point should be rejected"
);
assert!(
!crypto_core_ed25519_is_valid_point_relaxed(&torsion_bytes),
"Torsion point should be rejected even with relaxed validation"
);
let mixed_bytes = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion_point)
.compress()
.to_bytes();
assert!(
!crypto_core_ed25519_is_valid_point(&mixed_bytes),
"Mixed-order point should be rejected"
);
assert!(
!crypto_core_ed25519_is_valid_point_relaxed(&mixed_bytes),
"Mixed-order point should be rejected even with relaxed validation"
);
let mut point_not_on_curve = [0u8; CRYPTO_CORE_ED25519_BYTES];
point_not_on_curve[0] = 2; assert!(
!crypto_core_ed25519_is_valid_point(&point_not_on_curve),
"Point not on the curve should be invalid"
);
let zero_point = [0u8; CRYPTO_CORE_ED25519_BYTES];
assert!(
!crypto_core_ed25519_is_valid_point(&zero_point),
"Zero point represents invalid encoding"
);
}
#[test]
fn test_keypair_on_curve() {
let iterations = 25;
let mut strict_failures = 0;
let mut relaxed_failures = 0;
println!(
"\n=== Testing Ed25519 key validation across {} iterations ===",
iterations
);
for i in 0..iterations {
let (ed25519_pk, _) = crypto_sign_keypair();
let strict_valid = crypto_core_ed25519_is_valid_point(&ed25519_pk);
let relaxed_valid = crypto_core_ed25519_is_valid_point_relaxed(&ed25519_pk);
if !strict_valid {
strict_failures += 1;
let high_bit_set = ed25519_pk[CRYPTO_CORE_ED25519_BYTES - 1] & 0x80 != 0;
println!("Iteration {}: Ed25519 key strict validation failed:", i);
println!(" High bit set: {}", high_bit_set);
}
if !relaxed_valid {
relaxed_failures += 1;
println!(
"ERROR: Iteration {}: Ed25519 key failed relaxed validation",
i
);
const ZERO_POINT: Ed25519Point = [0u8; CRYPTO_CORE_ED25519_BYTES];
let is_zero = ed25519_pk == ZERO_POINT;
const SMALL_ORDER_POINT_IDENTITY: Ed25519Point = [
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, 0, 0,
];
let is_identity = ed25519_pk == SMALL_ORDER_POINT_IDENTITY;
let on_curve = match CompressedEdwardsY::from_slice(&ed25519_pk) {
Ok(compressed) => compressed.decompress().is_some(),
Err(_) => false,
};
println!(" Zero point: {}", is_zero);
println!(" Identity element: {}", is_identity);
println!(" On curve: {}", on_curve);
println!(" Key: {:?}", ed25519_pk);
}
assert!(
relaxed_valid,
"Generated Ed25519 key failed relaxed validation"
);
}
println!(
"\nSummary: {} of {} Ed25519 keys failed strict validation",
strict_failures, iterations
);
println!(
"Summary: {} of {} Ed25519 keys failed relaxed validation",
relaxed_failures, iterations
);
println!("\n=== Testing X25519 key validation ===");
let (x25519_pk, _) = crypto_box_keypair();
assert!(
KeyPair::<PublicKey, SecretKey>::is_valid_public_key(&x25519_pk),
"X25519 public key should be valid according to X25519 rules"
);
println!("X25519 key validation: Success");
}
}