use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use subtle::ConstantTimeEq;
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::error::Error;
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],
) -> Result<(), Error> {
crypto_scalarmult_curve25519(q, n, p);
if q.ct_eq(&[0u8; CRYPTO_SCALARMULT_BYTES]).into() {
Err(Error::invalid_key(crate::ErrorContext::Curve25519PublicKey))
} else {
Ok(())
}
}
#[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();
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 {
let Some(point) = decompress_canonical_ed25519_point(p) else {
return false;
};
!point.is_small_order() && point.is_torsion_free()
}
pub(crate) fn decompress_canonical_ed25519_point(p: &Ed25519Point) -> Option<EdwardsPoint> {
let compressed = CompressedEdwardsY(*p);
let point = compressed.decompress()?;
if point.compress() == compressed {
Some(point)
} else {
None
}
}
#[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_sign::crypto_sign_keypair;
#[test]
fn test_crypto_core_ed25519_is_valid_point() {
let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
assert!(crypto_core_ed25519_is_valid_point(&basepoint));
let mut negative_basepoint = basepoint;
negative_basepoint[31] |= 0x80;
assert!(
crypto_core_ed25519_is_valid_point(&negative_basepoint),
"the high bit is a valid x-coordinate sign bit"
);
let identity = {
let mut point = [0u8; CRYPTO_CORE_ED25519_BYTES];
point[0] = 1;
point
};
assert!(!crypto_core_ed25519_is_valid_point(&identity));
let noncanonical_identity = {
let mut point = [0xff; CRYPTO_CORE_ED25519_BYTES];
point[0] = 0xee;
point[31] = 0x7f;
point
};
assert!(
decompress_canonical_ed25519_point(&noncanonical_identity).is_none(),
"p + 1 must not be accepted as an alternate encoding of the identity"
);
assert!(!crypto_core_ed25519_is_valid_point(&noncanonical_identity));
let torsion = curve25519_dalek::constants::EIGHT_TORSION[1];
assert!(torsion.is_small_order());
assert!(!crypto_core_ed25519_is_valid_point(
&torsion.compress().to_bytes()
));
let mixed_order = curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion;
assert!(!mixed_order.is_small_order());
assert!(!mixed_order.is_torsion_free());
assert!(!crypto_core_ed25519_is_valid_point(
&mixed_order.compress().to_bytes()
));
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));
assert!(!crypto_core_ed25519_is_valid_point(
&[0u8; CRYPTO_CORE_ED25519_BYTES]
));
}
#[test]
fn test_generated_ed25519_keys_are_valid_points() {
for _ in 0..25 {
let (ed25519_pk, _) = crypto_sign_keypair();
assert!(crypto_core_ed25519_is_valid_point(&ed25519_pk));
}
}
#[test]
fn test_crypto_core_ed25519_rejects_legacy_libsodium_mixed_order_points() {
let mut y_is_nine = [0u8; CRYPTO_CORE_ED25519_BYTES];
y_is_nine[0] = 9;
let mut order_two_coset = [0x99; CRYPTO_CORE_ED25519_BYTES];
order_two_coset[0] = 0x95;
for point in [y_is_nine, order_two_coset] {
let decoded = decompress_canonical_ed25519_point(&point)
.expect("regression vector must be a canonical curve point");
assert!(!decoded.is_small_order());
assert!(!decoded.is_torsion_free());
assert!(!crypto_core_ed25519_is_valid_point(&point));
}
}
#[test]
fn test_crypto_scalarmult_rejects_low_order_points() {
let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
let mut one = [0u8; CRYPTO_SCALARMULT_BYTES];
one[0] = 1;
for public_key in [[0u8; CRYPTO_SCALARMULT_BYTES], one] {
let mut shared_secret = [0xa5; CRYPTO_SCALARMULT_BYTES];
crypto_scalarmult(&mut shared_secret, &scalar, &public_key)
.expect_err("low-order public key must be rejected");
assert_eq!(shared_secret, [0u8; CRYPTO_SCALARMULT_BYTES]);
}
}
#[test]
fn test_crypto_scalarmult_ignores_public_key_high_bit() {
let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
let mut canonical = [0u8; CRYPTO_SCALARMULT_BYTES];
canonical[0] = 9;
let mut high_bit_set = canonical;
high_bit_set[CRYPTO_SCALARMULT_BYTES - 1] = 0x80;
let mut canonical_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
let mut high_bit_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
crypto_scalarmult(&mut canonical_secret, &scalar, &canonical).unwrap();
crypto_scalarmult(&mut high_bit_secret, &scalar, &high_bit_set).unwrap();
assert_eq!(canonical_secret, high_bit_secret);
}
#[cfg(dryoc_native_tests)]
mod native_tests {
use super::*;
use crate::classic::crypto_box::*;
#[test]
fn test_crypto_core_ed25519_compatibility_for_version_stable_points() {
use libsodium_sys::crypto_core_ed25519_is_valid_point as sodium_is_valid_point;
let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
let mut negative_basepoint = basepoint;
negative_basepoint[31] |= 0x80;
let identity = {
let mut point = [0u8; CRYPTO_CORE_ED25519_BYTES];
point[0] = 1;
point
};
let noncanonical_identity = {
let mut point = [0xff; CRYPTO_CORE_ED25519_BYTES];
point[0] = 0xee;
point[31] = 0x7f;
point
};
let torsion = curve25519_dalek::constants::EIGHT_TORSION[1];
let mixed_order = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT + torsion)
.compress()
.to_bytes();
for point in [
basepoint,
negative_basepoint,
identity,
noncanonical_identity,
torsion.compress().to_bytes(),
mixed_order,
[0u8; CRYPTO_CORE_ED25519_BYTES],
] {
let dryoc_result = crypto_core_ed25519_is_valid_point(&point);
let sodium_result = unsafe { sodium_is_valid_point(point.as_ptr()) } == 1;
assert_eq!(dryoc_result, sodium_result, "point: {point:02x?}");
}
for _ in 0..20 {
let (public_key, _) = crypto_sign_keypair();
let sodium_result = unsafe { sodium_is_valid_point(public_key.as_ptr()) } == 1;
assert!(sodium_result);
assert_eq!(
crypto_core_ed25519_is_valid_point(&public_key),
sodium_result
);
}
}
#[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)
.expect("scalarmult failed");
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_scalarmult_low_order_compatibility() {
use sodiumoxide::crypto::scalarmult::curve25519::{GroupElement, Scalar, scalarmult};
let scalar = [0x42; CRYPTO_SCALARMULT_SCALARBYTES];
let mut one = [0u8; CRYPTO_SCALARMULT_BYTES];
one[0] = 1;
for public_key in [[0u8; CRYPTO_SCALARMULT_BYTES], one] {
let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
assert!(crypto_scalarmult(&mut shared_secret, &scalar, &public_key).is_err());
assert!(
scalarmult(
&Scalar::from_slice(&scalar).unwrap(),
&GroupElement::from_slice(&public_key).unwrap(),
)
.is_err()
);
}
}
#[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)
);
}
}
}
}