use std::fmt;
use eccoxide::protocol::x448;
use packtool::Packed;
use rand_core::{CryptoRng, RngCore};
use super::{Curve, DhCurve, SharedSecret};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid public key length: expected 56 bytes, got {0}")]
InvalidPublicKeyLength(usize),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct X448;
impl Curve for X448 {
const NAME: &'static str = "448";
const PUBLIC_KEY_SIZE: usize = 56;
const PRIVATE_KEY_SIZE: usize = 56;
type Error = Error;
type PublicKey = X448PublicKey;
fn public_key_from_bytes(bytes: &[u8]) -> Result<Self::PublicKey, Self::Error> {
X448PublicKey::from_bytes(bytes)
}
}
impl DhCurve for X448 {
const DHLEN: usize = 56;
type SharedSecret = SharedSecret<56>;
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Packed)]
pub struct X448PublicKey(#[packed(accessor = false)] [u8; 56]);
impl X448PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let arr: [u8; 56] = bytes
.try_into()
.map_err(|_| Error::InvalidPublicKeyLength(bytes.len()))?;
Ok(Self(arr))
}
pub fn as_bytes(&self) -> &[u8; 56] {
&self.0
}
}
impl AsRef<[u8]> for X448PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for X448PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
impl fmt::Debug for X448PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
pub struct SoftwareX448PrivateKey {
secret: [u8; 56],
}
impl SoftwareX448PrivateKey {
pub fn generate<R: RngCore + CryptoRng>(mut rng: R) -> Self {
let mut secret = [0u8; 56];
rng.fill_bytes(&mut secret);
Self { secret }
}
pub fn from_bytes(secret: [u8; 56]) -> Self {
Self { secret }
}
pub fn public_key(&self) -> X448PublicKey {
let public = x448::SecretKey::from_bytes(self.secret)
.public_key()
.to_bytes();
X448PublicKey(public)
}
pub fn dh(&self, peer: &X448PublicKey) -> SharedSecret<56> {
let shared = x448::SecretKey::from_bytes(self.secret)
.diffie_hellman(&x448::PublicKey::from_bytes(peer.0))
.to_bytes();
SharedSecret::new(shared)
}
pub fn as_bytes(&self) -> &[u8; 56] {
&self.secret
}
}
impl Drop for SoftwareX448PrivateKey {
fn drop(&mut self) {
crate::zeroize::zeroize_array(&mut self.secret);
}
}
#[cfg(not(test))]
impl fmt::Debug for SoftwareX448PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoftwareX448PrivateKey")
.finish_non_exhaustive()
}
}
#[cfg(test)]
impl fmt::Debug for SoftwareX448PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoftwareX448PrivateKey")
.field("secret", &hex::encode(self.secret))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn h56(s: &str) -> [u8; 56] {
hex::decode(s).unwrap().try_into().unwrap()
}
#[test]
fn rfc7748_section_6_2_ecdh_known_answer() {
let alice_sk = h56("9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d\
d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b");
let alice_pk = h56("9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c\
22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0");
let bob_sk = h56("1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d\
6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d");
let bob_pk = h56("3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430\
27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609");
let shared = h56("07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282b\
b60c0b56fd2464c335543936521c24403085d59a449a5037514a879d");
let alice = SoftwareX448PrivateKey::from_bytes(alice_sk);
let bob = SoftwareX448PrivateKey::from_bytes(bob_sk);
assert_eq!(alice.public_key().as_bytes(), &alice_pk);
assert_eq!(bob.public_key().as_bytes(), &bob_pk);
let ss_ab = alice.dh(&X448PublicKey::from_bytes(&bob_pk).unwrap());
let ss_ba = bob.dh(&X448PublicKey::from_bytes(&alice_pk).unwrap());
assert_eq!(ss_ab.as_bytes(), &shared);
assert_eq!(ss_ba.as_bytes(), &shared);
}
#[test]
fn rfc7748_section_5_2_scalarmult_known_answer() {
let scalar = h56("3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121\
700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3");
let u = h56("06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9\
814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086");
let expected = h56("ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239f\
e14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f");
let sk = SoftwareX448PrivateKey::from_bytes(scalar);
let out = sk.dh(&X448PublicKey::from_bytes(&u).unwrap());
assert_eq!(out.as_bytes(), &expected);
}
#[test]
fn dh_is_symmetric() {
let sk1 = SoftwareX448PrivateKey::generate(rand::rng());
let pk1 = sk1.public_key();
let sk2 = SoftwareX448PrivateKey::generate(rand::rng());
let pk2 = sk2.public_key();
assert_eq!(sk1.dh(&pk2), sk2.dh(&pk1));
}
#[test]
fn dh_different_peers_produce_different_secrets() {
let sk = SoftwareX448PrivateKey::generate(rand::rng());
let peer1 = SoftwareX448PrivateKey::generate(rand::rng()).public_key();
let peer2 = SoftwareX448PrivateKey::generate(rand::rng()).public_key();
assert_ne!(sk.dh(&peer1), sk.dh(&peer2));
}
#[test]
fn public_key_round_trip() {
let sk = SoftwareX448PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let pk2 = X448PublicKey::from_bytes(pk.as_bytes()).unwrap();
assert_eq!(pk, pk2);
}
#[test]
fn public_key_wrong_length_rejected() {
let err = X448PublicKey::from_bytes(&[0u8; 55]).unwrap_err();
assert!(matches!(err, Error::InvalidPublicKeyLength(55)));
let err = X448PublicKey::from_bytes(&[0u8; 57]).unwrap_err();
assert!(matches!(err, Error::InvalidPublicKeyLength(57)));
}
#[test]
fn low_order_point_yields_zero_secret_per_spec() {
let sk = SoftwareX448PrivateKey::generate(rand::rng());
let low_order = X448PublicKey::from_bytes(&[0u8; 56]).unwrap();
assert_eq!(sk.dh(&low_order).as_bytes(), &[0u8; 56]);
}
proptest! {
#[test]
fn dh_never_panics(peer_bytes in any::<[u8; 56]>(), secret in any::<[u8; 56]>()) {
let sk = SoftwareX448PrivateKey::from_bytes(secret);
let peer = X448PublicKey::from_bytes(&peer_bytes).unwrap();
let _ = sk.dh(&peer);
}
}
}