#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X25519Error {
InvalidInput,
ValidationError(String),
}
impl std::fmt::Display for X25519Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
X25519Error::InvalidInput => write!(f, "Invalid input: scalar or u-coordinate is all zeros"),
X25519Error::ValidationError(msg) => write!(f, "Validation error: {}", msg),
}
}
}
impl std::error::Error for X25519Error {}
pub type X25519Result<T> = Result<T, X25519Error>;
const CURVE25519_BIT_LEN: usize = 255;
pub const CURVE25519_BYTE_LEN: usize = 32;
const CURVE25519_WORD_LEN: usize = 8;
const CURVE25519_A24: u32 = 121666;
pub const U_COORDINATE: [u8; CURVE25519_BYTE_LEN] = [
9, 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,
];
#[repr(align(32))]
struct X25519State {
k: [u32; CURVE25519_WORD_LEN],
u: [u32; CURVE25519_WORD_LEN],
x1: [u32; CURVE25519_WORD_LEN],
z1: [u32; CURVE25519_WORD_LEN],
x2: [u32; CURVE25519_WORD_LEN],
z2: [u32; CURVE25519_WORD_LEN],
t1: [u32; CURVE25519_WORD_LEN],
t2: [u32; CURVE25519_WORD_LEN],
}
impl Drop for X25519State {
fn drop(&mut self) {
let raw = self as *mut X25519State as *mut [u8; 256];
unsafe { *raw = [0; 256] };
}
}
#[inline]
fn validate_input(input: &[u8; CURVE25519_BYTE_LEN]) -> X25519Result<()> {
let is_zero = input.iter().all(|&b| b == 0);
if is_zero {
return Err(X25519Error::InvalidInput);
}
Ok(())
}
#[inline]
fn bytes_to_u32_array(bytes: [u8; CURVE25519_BYTE_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut result = [0u32; CURVE25519_WORD_LEN];
for (i, chunk) in bytes.chunks_exact(4).enumerate() {
result[i] = u32::from_le_bytes(chunk.try_into().unwrap());
}
result
}
#[inline]
fn u32_array_to_bytes(array: [u32; CURVE25519_WORD_LEN]) -> [u8; CURVE25519_BYTE_LEN] {
let mut result = [0u8; CURVE25519_BYTE_LEN];
for (i, &word) in array.iter().enumerate() {
let bytes = word.to_le_bytes();
result[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
}
result
}
pub fn x25519(k: [u8; CURVE25519_BYTE_LEN], u: [u8; CURVE25519_BYTE_LEN]) -> X25519Result<[u8; CURVE25519_BYTE_LEN]> {
validate_input(&k)?;
validate_input(&u)?;
let mut swap: u32 = 0;
let mut b: u32;
let mut state = X25519State {
k: [0; CURVE25519_WORD_LEN],
u: [0; CURVE25519_WORD_LEN],
x1: [0; CURVE25519_WORD_LEN],
z1: [0; CURVE25519_WORD_LEN],
x2: [0; CURVE25519_WORD_LEN],
z2: [0; CURVE25519_WORD_LEN],
t1: [0; CURVE25519_WORD_LEN],
t2: [0; CURVE25519_WORD_LEN],
};
state.k = bytes_to_u32_array(k);
state.k[0] &= 0xFFFFFFF8;
state.k[7] &= 0x7FFFFFFF;
state.k[7] |= 0x40000000;
state.u = bytes_to_u32_array(u);
state.u[7] &= 0x7FFFFFFF;
state.u = curve25519_red(state.u);
state.x1[0] = 1;
state.x2 = state.u;
state.z2[0] = 1;
for i in (0usize..CURVE25519_BIT_LEN).rev() {
b = (state.k[i / 32] >> (i % 32)) & 1;
curve25519_swap(&mut state.x1, &mut state.x2, swap ^ b);
curve25519_swap(&mut state.z1, &mut state.z2, swap ^ b);
swap = b;
state.t1 = curve25519_add(state.x2, state.z2);
state.x2 = curve25519_sub(state.x2, state.z2);
state.z2 = curve25519_add(state.x1, state.z1);
state.x1 = curve25519_sub(state.x1, state.z1);
state.t1 = curve25519_mul(state.t1, state.x1);
state.x2 = curve25519_mul(state.x2, state.z2);
state.z2 = curve25519_sqr(state.z2);
state.x1 = curve25519_sqr(state.x1);
state.t2 = curve25519_sub(state.z2, state.x1);
state.z1 = curve25519_mul_int(state.t2, CURVE25519_A24);
state.z1 = curve25519_add(state.z1, state.x1);
state.z1 = curve25519_mul(state.z1, state.t2);
state.x1 = curve25519_mul(state.x1, state.z2);
state.z2 = curve25519_sub(state.t1, state.x2);
state.z2 = curve25519_sqr(state.z2);
state.z2 = curve25519_mul(state.z2, state.u);
state.x2 = curve25519_add(state.x2, state.t1);
state.x2 = curve25519_sqr(state.x2);
}
curve25519_swap(&mut state.x1, &mut state.x2, swap);
curve25519_swap(&mut state.z1, &mut state.z2, swap);
state.u = curve25519_inv(state.z1);
state.u = curve25519_mul(state.u, state.x1);
Ok(u32_array_to_bytes(state.u))
}
#[inline]
fn curve25519_red(a: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut temp: u64 = 19;
let mut b: [u32; CURVE25519_WORD_LEN] = Default::default();
for i in 0..CURVE25519_WORD_LEN {
temp += a[i] as u64;
b[i] = temp as u32;
temp >>= 32;
}
b[7] = b[7].wrapping_sub(0x80000000);
curve25519_select(&b, &a, (b[7] & 0x80000000) >> 31)
}
#[inline]
fn curve25519_select(a: &[u32; CURVE25519_WORD_LEN], b: &[u32; CURVE25519_WORD_LEN], c: u32) -> [u32; CURVE25519_WORD_LEN] {
let mask = c.wrapping_sub(1);
let mut r: [u32; CURVE25519_WORD_LEN] = Default::default();
for i in 0..CURVE25519_WORD_LEN {
r[i] = (a[i] & mask) | (b[i] & !mask);
}
r
}
#[inline]
fn curve25519_swap(a: &mut [u32; CURVE25519_WORD_LEN], b: &mut [u32; CURVE25519_WORD_LEN], c: u32) {
let mut dummy: u32;
let mask = (!c).wrapping_add(1);
for i in 0..CURVE25519_WORD_LEN {
dummy = mask & (a[i] ^ b[i]);
a[i] ^= dummy;
b[i] ^= dummy;
}
}
#[inline]
fn curve25519_add(a: [u32; CURVE25519_WORD_LEN], b: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut temp: u64 = 0;
let mut r: [u32; CURVE25519_WORD_LEN] = Default::default();
for i in 0..CURVE25519_WORD_LEN {
temp += a[i] as u64;
temp += b[i] as u64;
r[i] = temp as u32;
temp >>= 32;
}
curve25519_red(r)
}
#[inline]
fn curve25519_sub(a: [u32; CURVE25519_WORD_LEN], b: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut temp: i64 = -19;
let mut result: [u32; CURVE25519_WORD_LEN] = Default::default();
for i in 0..CURVE25519_WORD_LEN {
temp += a[i] as i64;
temp -= b[i] as i64;
result[i] = temp as u32;
temp >>= 32;
}
result[7] = result[7].wrapping_add(0x80000000);
curve25519_red(result)
}
#[inline]
fn curve25519_mul(a: [u32; CURVE25519_WORD_LEN], b: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut c: u64 = 0;
let mut temp: u64 = 0;
let mut u: [u32; 16] = Default::default();
for i in 0..16 {
if i < CURVE25519_WORD_LEN {
for j in 0..=i {
temp += a[j] as u64 * b[i - j] as u64;
c += temp >> 32;
temp &= 0xFFFFFFFF;
}
} else {
for j in i - 7..CURVE25519_WORD_LEN {
temp += a[j] as u64 * b[i - j] as u64;
c += temp >> 32;
temp &= 0xFFFFFFFF;
}
}
u[i] = temp as u32;
temp = c & 0xFFFFFFFF;
c >>= 32;
}
temp = (u[7] >> 31) as u64 * 19;
u[7] &= 0x7FFFFFFF;
for i in 0..CURVE25519_WORD_LEN {
temp += u[i] as u64;
temp += u[i + CURVE25519_WORD_LEN] as u64 * 38;
u[i] = temp as u32;
temp >>= 32;
}
temp *= 38;
temp += (u[7] >> 31) as u64 * 19;
u[7] &= 0x7FFFFFFF;
for i in 0..CURVE25519_WORD_LEN {
temp += u[i] as u64;
u[i] = temp as u32;
temp >>= 32;
}
let mut temp: [u32; CURVE25519_WORD_LEN] = Default::default();
temp.copy_from_slice(&u[..CURVE25519_WORD_LEN]);
curve25519_red(temp)
}
#[inline]
fn curve25519_sqr(a: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
curve25519_mul(a, a)
}
#[inline]
fn curve25519_mul_int(a: [u32; CURVE25519_WORD_LEN], b: u32) -> [u32; CURVE25519_WORD_LEN] {
let mut temp: u64 = 0;
let mut u: [u32; CURVE25519_WORD_LEN] = Default::default();
for i in 0..CURVE25519_WORD_LEN {
temp += a[i] as u64 * b as u64;
u[i] = temp as u32;
temp >>= 32;
}
temp *= 38;
temp += (u[7] >> 31) as u64 * 19;
u[7] &= 0x7FFFFFFF;
for i in 0..CURVE25519_WORD_LEN {
temp += u[i] as u64;
u[i] = temp as u32;
temp >>= 32;
}
curve25519_red(u)
}
#[inline]
fn curve25519_inv(a: [u32; CURVE25519_WORD_LEN]) -> [u32; CURVE25519_WORD_LEN] {
let mut u: [u32; CURVE25519_WORD_LEN];
let mut v: [u32; CURVE25519_WORD_LEN];
u = curve25519_sqr(a);
u = curve25519_mul(u, a); u = curve25519_sqr(u);
v = curve25519_mul(u, a);
u = curve25519_pwr2(v, 3);
u = curve25519_mul(u, v); u = curve25519_sqr(u);
v = curve25519_mul(u, a);
u = curve25519_pwr2(v, 7);
u = curve25519_mul(u, v); u = curve25519_sqr(u);
v = curve25519_mul(u, a);
u = curve25519_pwr2(v, 15);
u = curve25519_mul(u, v); u = curve25519_sqr(u);
v = curve25519_mul(u, a);
u = curve25519_pwr2(v, 31);
v = curve25519_mul(u, v);
u = curve25519_pwr2(v, 62);
u = curve25519_mul(u, v); u = curve25519_sqr(u);
v = curve25519_mul(u, a);
u = curve25519_pwr2(v, 125);
u = curve25519_mul(u, v); u = curve25519_sqr(u);
u = curve25519_sqr(u);
u = curve25519_mul(u, a);
u = curve25519_sqr(u);
u = curve25519_sqr(u);
u = curve25519_mul(u, a);
u = curve25519_sqr(u);
curve25519_mul(u, a) }
#[inline]
fn curve25519_pwr2(a: [u32; CURVE25519_WORD_LEN], n: usize) -> [u32; CURVE25519_WORD_LEN] {
let mut result = curve25519_sqr(a);
for _ in 1..n {
result = curve25519_sqr(result);
}
result
}
#[cfg(test)]
pub fn generate_private_key<R: rand::RngCore + rand::CryptoRng>(rng: &mut R) -> X25519Result<[u8; CURVE25519_BYTE_LEN]> {
let mut key = [0u8; CURVE25519_BYTE_LEN];
rng.fill_bytes(&mut key);
if key.iter().all(|&b| b == 0) {
return Err(X25519Error::ValidationError("Generated key is all zeros".to_string()));
}
key[0] &= 0xf8; key[31] &= 0x7f; key[31] |= 0x40;
Ok(key)
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn test_x25519() {
let scalar = [
0xa5u8, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46,
0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44,
0xba, 0x44, 0x9a, 0xc4,
];
let u_coordinate = [
0xe6u8, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1,
0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6,
0xd0, 0xab, 0x1c, 0x4c,
];
let result = x25519(scalar, u_coordinate).unwrap();
assert_eq!(
result,
[
0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d,
0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55,
0x77, 0xa2, 0x85, 0x52
]
);
let scalar = [
0x4b, 0x66, 0xe9, 0xd4, 0xd1, 0xb4, 0x67, 0x3c, 0x5a, 0xd2, 0x26, 0x91, 0x95, 0x7d,
0x6a, 0xf5, 0xc1, 0x1b, 0x64, 0x21, 0xe0, 0xea, 0x01, 0xd4, 0x2c, 0xa4, 0x16, 0x9e,
0x79, 0x18, 0xba, 0x0d,
];
let u_coordinate = [
0xe5, 0x21, 0x0f, 0x12, 0x78, 0x68, 0x11, 0xd3, 0xf4, 0xb7, 0x95, 0x9d, 0x05, 0x38,
0xae, 0x2c, 0x31, 0xdb, 0xe7, 0x10, 0x6f, 0xc0, 0x3c, 0x3e, 0xfc, 0x4c, 0xd5, 0x49,
0xc7, 0x15, 0xa4, 0x93,
];
let result = x25519(scalar, u_coordinate).unwrap();
assert_eq!(
result,
[
0x95, 0xcb, 0xde, 0x94, 0x76, 0xe8, 0x90, 0x7d, 0x7a, 0xad, 0xe4, 0x5c, 0xb4, 0xb8,
0x73, 0xf8, 0x8b, 0x59, 0x5a, 0x68, 0x79, 0x9f, 0xa1, 0x52, 0xe6, 0xf8, 0xf7, 0x64,
0x7a, 0xac, 0x79, 0x57
]
);
}
#[test]
fn test_x25519_series() {
let scalar = U_COORDINATE;
let u_coordinate = U_COORDINATE;
let result = x25519(scalar, u_coordinate).unwrap();
assert_eq!(
result,
[
0x42, 0x2c, 0x8e, 0x7a, 0x62, 0x27, 0xd7, 0xbc, 0xa1, 0x35, 0x0b, 0x3e, 0x2b, 0xb7,
0x27, 0x9f, 0x78, 0x97, 0xb8, 0x7b, 0xb6, 0x85, 0x4b, 0x78, 0x3c, 0x60, 0xe8, 0x03,
0x11, 0xae, 0x30, 0x79
]
);
}
#[test]
fn test_generate_private_key() {
let mut rng = OsRng;
let private_key = generate_private_key(&mut rng).unwrap();
assert!(!private_key.iter().all(|&b| b == 0));
assert_eq!(private_key[0] & 0x07, 0); assert_eq!(private_key[31] & 0x80, 0); assert_eq!(private_key[31] & 0x40, 0x40); }
#[test]
fn test_invalid_input() {
let zero_input = [0u8; CURVE25519_BYTE_LEN];
let valid_input = U_COORDINATE;
assert!(matches!(x25519(zero_input, valid_input), Err(X25519Error::InvalidInput)));
assert!(matches!(x25519(valid_input, zero_input), Err(X25519Error::InvalidInput)));
assert!(matches!(x25519(zero_input, zero_input), Err(X25519Error::InvalidInput)));
}
#[test]
fn test_key_exchange() {
let mut rng = OsRng;
let alice_private = generate_private_key(&mut rng).unwrap();
let bob_private = generate_private_key(&mut rng).unwrap();
let alice_public = x25519(alice_private, U_COORDINATE).unwrap();
let bob_public = x25519(bob_private, U_COORDINATE).unwrap();
let alice_shared = x25519(alice_private, bob_public).unwrap();
let bob_shared = x25519(bob_private, alice_public).unwrap();
assert_eq!(alice_shared, bob_shared);
}
}