use super::field::*;
fn a24() -> FieldElement<7> {
let mut fe = FieldElement::<7>::ZERO;
fe.limbs[0] = 39_081;
fe
}
const BASE_U: [u8; 56] = {
let mut b = [0u8; 56];
b[0] = 5;
b
};
fn decode_scalar(scalar: &[u8; 56]) -> [u8; 56] {
let mut k = *scalar;
k[0] &= 252; k[55] |= 128; k
}
fn decode_u(u: &[u8; 56]) -> FieldElement<7> {
FieldElement::<7>::from_bytes_le(u)
}
fn encode_u(fe: &FieldElement<7>) -> [u8; 56] {
let v = fe.to_bytes_le();
let mut out = [0u8; 56];
out.copy_from_slice(&v);
out
}
fn ct_swap_fe(a: &mut FieldElement<7>, b: &mut FieldElement<7>, swap: u64) {
let mask = 0u64.wrapping_sub(swap);
for i in 0..7 {
let t = mask & (a.limbs[i] ^ b.limbs[i]);
a.limbs[i] ^= t;
b.limbs[i] ^= t;
}
}
pub fn x448(scalar: &[u8; 56], u: &[u8; 56]) -> [u8; 56] {
let k = decode_scalar(scalar);
let x1 = decode_u(u);
let a24 = a24();
let p = &CURVE448_P;
let mut x_2 = FieldElement::<7>::one();
let mut z_2 = FieldElement::<7>::ZERO;
let mut x_3 = x1;
let mut z_3 = FieldElement::<7>::one();
let mut swap: u64 = 0;
for t in (0..=447).rev() {
let k_t = ((k[t >> 3] >> (t & 7)) & 1) as u64;
swap ^= k_t;
ct_swap_fe(&mut x_2, &mut x_3, swap);
ct_swap_fe(&mut z_2, &mut z_3, swap);
swap = k_t;
let a = field_add(&x_2, &z_2, p);
let aa = field_sqr(&a, p);
let b = field_sub(&x_2, &z_2, p);
let bb = field_sqr(&b, p);
let e = field_sub(&aa, &bb, p);
let c = field_add(&x_3, &z_3, p);
let d = field_sub(&x_3, &z_3, p);
let da = field_mul(&d, &a, p);
let cb = field_mul(&c, &b, p);
let da_plus_cb = field_add(&da, &cb, p);
x_3 = field_sqr(&da_plus_cb, p);
let da_minus_cb = field_sub(&da, &cb, p);
let da_minus_cb_sq = field_sqr(&da_minus_cb, p);
z_3 = field_mul(&x1, &da_minus_cb_sq, p);
x_2 = field_mul(&aa, &bb, p);
let a24_e = field_mul(&a24, &e, p);
let aa_plus_a24e = field_add(&aa, &a24_e, p);
z_2 = field_mul(&e, &aa_plus_a24e, p);
}
ct_swap_fe(&mut x_2, &mut x_3, swap);
ct_swap_fe(&mut z_2, &mut z_3, swap);
let z_inv = field_inv(&z_2, p);
let result = field_mul(&x_2, &z_inv, p);
encode_u(&result)
}
pub fn x448_derive_public(sk: &[u8; 56]) -> [u8; 56] {
x448(sk, &BASE_U)
}
pub fn x448_ecdh(sk: &[u8; 56], peer_pk: &[u8; 56]) -> [u8; 56] {
x448(sk, peer_pk)
}
#[cfg(test)]
mod tests {
use super::*;
fn hex56(h: &str) -> [u8; 56] {
assert_eq!(h.len(), 112);
let mut out = [0u8; 56];
for i in 0..56 {
out[i] = u8::from_str_radix(&h[2 * i..2 * i + 2], 16).unwrap();
}
out
}
#[test]
fn rfc7748_section_5_2_vector_1() {
let scalar = hex56(
"3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3",
);
let u = hex56(
"06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086",
);
let expected = hex56(
"ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f",
);
assert_eq!(x448(&scalar, &u), expected);
}
#[test]
fn rfc7748_section_5_2_vector_2() {
let scalar = hex56(
"203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f",
);
let u = hex56(
"0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db",
);
let expected = hex56(
"884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d",
);
assert_eq!(x448(&scalar, &u), expected);
}
#[test]
fn rfc7748_section_6_2_alice_pk() {
let alice_sk = hex56(
"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b",
);
let expected = hex56(
"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0",
);
assert_eq!(x448_derive_public(&alice_sk), expected);
}
#[test]
fn rfc7748_section_6_2_bob_pk() {
let bob_sk = hex56(
"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d",
);
let expected = hex56(
"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609",
);
assert_eq!(x448_derive_public(&bob_sk), expected);
}
#[test]
fn rfc7748_section_6_2_shared_secret() {
let alice_sk = hex56(
"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b",
);
let bob_sk = hex56(
"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d",
);
let alice_pk = x448_derive_public(&alice_sk);
let bob_pk = x448_derive_public(&bob_sk);
let expected = hex56(
"07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d",
);
assert_eq!(x448_ecdh(&alice_sk, &bob_pk), expected);
assert_eq!(x448_ecdh(&bob_sk, &alice_pk), expected);
}
#[test]
fn x448_roundtrip_custom_keys() {
let alice_sk = hex56(
"0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
);
let bob_sk = hex56(
"0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202",
);
let alice_pk = x448_derive_public(&alice_sk);
let bob_pk = x448_derive_public(&bob_sk);
let s_ab = x448_ecdh(&alice_sk, &bob_pk);
let s_ba = x448_ecdh(&bob_sk, &alice_pk);
assert_eq!(s_ab, s_ba);
assert!(s_ab.iter().any(|&b| b != 0));
}
#[test]
fn clamping_is_idempotent() {
let base = hex56(
"3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3",
);
let u = hex56(
"06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086",
);
let ref_out = x448(&base, &u);
let mut dirty = base;
dirty[0] |= 0b0000_0011; dirty[55] &= !0b1000_0000; let dirty_out = x448(&dirty, &u);
assert_eq!(ref_out, dirty_out);
}
}