use crate::bignum::{MontModulus, Uint};
use crate::ct::{Choice, ConditionallySelectable, ConstantTimeEq};
use crate::rng::RngCore;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum X448Error {
SmallOrderPeer,
}
impl core::fmt::Display for X448Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
X448Error::SmallOrderPeer => {
f.write_str("X448 peer public key is a small-order / contributory-failure point")
}
}
}
}
impl core::error::Error for X448Error {}
const P448_HEX: &str = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
const A24: u64 = 39081;
type Fe = Uint<7>;
fn fe_from_hex(hex: &str) -> Fe {
let h = hex.as_bytes();
let mut bytes = [0u8; 56];
let mut i = 0;
while i < 56 {
let hi = (h[2 * i] as char).to_digit(16).unwrap() as u8;
let lo = (h[2 * i + 1] as char).to_digit(16).unwrap() as u8;
bytes[i] = (hi << 4) | lo;
i += 1;
}
Fe::from_be_bytes(&bytes)
}
pub fn x448(scalar: &[u8; 56], point: &[u8; 56]) -> [u8; 56] {
let fp = MontModulus::new(fe_from_hex(P448_HEX));
let mut k = *scalar;
k[0] &= 252;
k[55] |= 128;
let k = Fe::from_le_bytes(&k);
let u = Fe::from_le_bytes(point).reduce(fp.modulus());
let one = fp.to_mont(&Fe::ONE);
let x1 = fp.to_mont(&u);
let mut x2 = one;
let mut z2 = Fe::ZERO;
let mut x3 = x1;
let mut z3 = one;
let a24 = fp.to_mont(&Fe::from_u64(A24));
let mul = |a: &Fe, b: &Fe| fp.mont_mul(a, b);
let add = |a: &Fe, b: &Fe| fp.add_mod(a, b);
let sub = |a: &Fe, b: &Fe| fp.sub_mod(a, b);
let mut swap = 0u8;
let limbs = k.as_limbs();
let mut t = 448;
while t > 0 {
t -= 1;
let kt = ((limbs[t / 64] >> (t % 64)) & 1) as u8;
swap ^= kt;
let sw = Choice::from(swap);
Fe::conditional_swap(&mut x2, &mut x3, sw);
Fe::conditional_swap(&mut z2, &mut z3, sw);
swap = kt;
let a = add(&x2, &z2);
let aa = mul(&a, &a);
let b = sub(&x2, &z2);
let bb = mul(&b, &b);
let e = sub(&aa, &bb);
let c = add(&x3, &z3);
let d = sub(&x3, &z3);
let da = mul(&d, &a);
let cb = mul(&c, &b);
let t0 = add(&da, &cb);
x3 = mul(&t0, &t0);
let t1 = sub(&da, &cb);
let t1sq = mul(&t1, &t1);
z3 = mul(&x1, &t1sq);
x2 = mul(&aa, &bb);
let t2 = add(&aa, &mul(&a24, &e));
z2 = mul(&e, &t2);
}
let sw = Choice::from(swap);
Fe::conditional_swap(&mut x2, &mut x3, sw);
Fe::conditional_swap(&mut z2, &mut z3, sw);
let z2_plain = fp.from_mont(&z2);
let p_minus_2 = fp.modulus().wrapping_sub(&Fe::from_u64(2));
let z_inv = fp.pow(&z2_plain, &p_minus_2);
let res = fp.mul_mod(&fp.from_mont(&x2), &z_inv);
let mut out = [0u8; 56];
res.write_le_bytes(&mut out);
out
}
pub const BASE_POINT: [u8; 56] = {
let mut b = [0u8; 56];
b[0] = 5;
b
};
#[derive(Clone)]
pub struct X448PrivateKey {
scalar: [u8; 56],
}
impl Drop for X448PrivateKey {
fn drop(&mut self) {
for b in self.scalar.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&self.scalar);
}
}
impl X448PrivateKey {
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
let mut scalar = [0u8; 56];
rng.fill_bytes(&mut scalar);
X448PrivateKey { scalar }
}
pub fn from_bytes(scalar: [u8; 56]) -> Self {
X448PrivateKey { scalar }
}
pub fn public_key(&self) -> [u8; 56] {
x448(&self.scalar, &BASE_POINT)
}
pub fn diffie_hellman(&self, peer: &[u8; 56]) -> Result<[u8; 56], X448Error> {
let out = x448(&self.scalar, peer);
if bool::from(out.ct_eq(&[0u8; 56])) {
Err(X448Error::SmallOrderPeer)
} else {
Ok(out)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::Sha256;
use crate::rng::HmacDrbg;
use crate::test_util::from_hex;
fn hex56(s: &str) -> [u8; 56] {
from_hex::<56>(s)
}
#[test]
fn rfc7748_test_vector() {
let scalar = hex56(
"3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121\
700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3",
);
let u = hex56(
"06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9\
814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086",
);
let out = x448(&scalar, &u);
assert_eq!(
out,
hex56(
"ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239f\
e14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f"
)
);
}
#[test]
fn rfc7748_test_vector_second() {
let scalar = hex56(
"203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c5\
38345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f",
);
let u = hex56(
"0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b\
165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db",
);
let out = x448(&scalar, &u);
assert_eq!(
out,
hex56(
"884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7\
ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d"
)
);
}
#[test]
fn rfc7748_iterated_one() {
let mut k = [0u8; 56];
k[0] = 5;
let u = k;
k = x448(&k, &u);
assert_eq!(
k,
hex56(
"3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a\
4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113"
)
);
}
#[test]
fn rfc7748_iterated_thousand() {
let mut k = [0u8; 56];
k[0] = 5;
let mut u = k;
for _ in 0..1000 {
let out = x448(&k, &u);
u = k;
k = out;
}
assert_eq!(
k,
hex56(
"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4\
af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38"
)
);
}
#[test]
#[ignore = "1,000,000-iteration RFC 7748 vector is slow; run explicitly"]
fn rfc7748_iterated_million() {
let mut k = [0u8; 56];
k[0] = 5;
let mut u = k;
for _ in 0..1_000_000 {
let out = x448(&k, &u);
u = k;
k = out;
}
assert_eq!(
k,
hex56(
"077f453681caca3693198420bbe515cae0002472519b3e67661a7e89\
cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37"
)
);
}
#[test]
fn rfc7748_diffie_hellman() {
let a = X448PrivateKey::from_bytes(hex56(
"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d\
d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b",
));
let b = X448PrivateKey::from_bytes(hex56(
"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d\
6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d",
));
assert_eq!(
a.public_key(),
hex56(
"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c\
22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0"
)
);
assert_eq!(
b.public_key(),
hex56(
"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430\
27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609"
)
);
let shared = hex56(
"07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282b\
b60c0b56fd2464c335543936521c24403085d59a449a5037514a879d",
);
assert_eq!(a.diffie_hellman(&b.public_key()).unwrap(), shared);
assert_eq!(b.diffie_hellman(&a.public_key()).unwrap(), shared);
}
#[test]
fn generated_keys_agree() {
let mut rng = HmacDrbg::<Sha256>::new(b"x448", b"nonce", &[]);
let a = X448PrivateKey::generate(&mut rng);
let b = X448PrivateKey::generate(&mut rng);
assert_eq!(
a.diffie_hellman(&b.public_key()).unwrap(),
b.diffie_hellman(&a.public_key()).unwrap()
);
}
}