use x448::{PublicKey as X448PublicKey, StaticSecret as X448StaticSecret};
use p521::NistP521;
use p521::elliptic_curve::FieldBytes;
use p521::elliptic_curve::ops::Reduce;
use p521::elliptic_curve::sec1::ToSec1Point;
use p521::{NonZeroScalar, PublicKey as P521PublicKey, Scalar, SecretKey as P521SecretKey};
use crate::CryptoError;
pub(crate) const X448_LEN: usize = 56;
#[inline]
fn random_bytes(buf: &mut [u8]) {
getrandom::getrandom(buf).expect("OS CSPRNG unavailable");
}
pub(crate) fn x448_public_from_secret(secret: &[u8; X448_LEN]) -> [u8; X448_LEN] {
let sk = X448StaticSecret::from(*secret);
*X448PublicKey::from(&sk).as_bytes()
}
pub(crate) fn x448_encapsulate(
recipient_pk: &[u8],
) -> Result<([u8; X448_LEN], [u8; X448_LEN]), CryptoError> {
let recipient = X448PublicKey::from_bytes(recipient_pk)
.ok_or_else(|| CryptoError::Hybrid("invalid X448 recipient public key".into()))?;
let mut eph_bytes = [0u8; X448_LEN];
random_bytes(&mut eph_bytes);
let eph_sk = X448StaticSecret::from(eph_bytes);
let eph_pk = *X448PublicKey::from(&eph_sk).as_bytes();
let ss = *eph_sk.diffie_hellman(&recipient).as_bytes();
Ok((eph_pk, ss))
}
pub(crate) fn x448_decapsulate(
recipient_secret: &[u8; X448_LEN],
eph_pk: &[u8],
) -> Result<[u8; X448_LEN], CryptoError> {
let eph = X448PublicKey::from_bytes(eph_pk)
.ok_or_else(|| CryptoError::Hybrid("invalid X448 ephemeral public key".into()))?;
let sk = X448StaticSecret::from(*recipient_secret);
Ok(*sk.diffie_hellman(&eph).as_bytes())
}
pub(crate) const P521_PK_LEN: usize = 133;
pub(crate) const P521_SK_LEN: usize = 66;
pub(crate) const P521_SS_LEN: usize = 66;
fn p521_secret_from_bytes(bytes: &[u8; P521_SK_LEN]) -> P521SecretKey {
let fb: FieldBytes<NistP521> = (*bytes).into();
let scalar = <Scalar as Reduce<FieldBytes<NistP521>>>::reduce(&fb);
let nz: NonZeroScalar =
Option::from(NonZeroScalar::new(scalar)).expect("P-521 scalar reduced to zero");
P521SecretKey::from(nz)
}
pub(crate) fn p521_public_from_secret(
secret: &[u8; P521_SK_LEN],
) -> Result<[u8; P521_PK_LEN], CryptoError> {
let sk = p521_secret_from_bytes(secret);
let ep = sk.public_key().to_sec1_point(false);
let bytes = ep.as_bytes();
bytes
.try_into()
.map_err(|_| CryptoError::Hybrid("unexpected P-521 public-key length".into()))
}
pub(crate) fn p521_encapsulate(
recipient_pk: &[u8],
) -> Result<([u8; P521_PK_LEN], [u8; P521_SS_LEN]), CryptoError> {
let recipient = P521PublicKey::from_sec1_bytes(recipient_pk)
.map_err(|_| CryptoError::Hybrid("invalid P-521 recipient public key".into()))?;
let mut eph_bytes = [0u8; P521_SK_LEN];
random_bytes(&mut eph_bytes);
let eph_sk = p521_secret_from_bytes(&eph_bytes);
let eph_pk: [u8; P521_PK_LEN] = eph_sk
.public_key()
.to_sec1_point(false)
.as_bytes()
.try_into()
.map_err(|_| CryptoError::Hybrid("unexpected P-521 public-key length".into()))?;
let ss: [u8; P521_SS_LEN] = (*eph_sk.diffie_hellman(&recipient).raw_secret_bytes()).into();
Ok((eph_pk, ss))
}
pub(crate) fn p521_decapsulate(
recipient_secret: &[u8; P521_SK_LEN],
eph_pk: &[u8],
) -> Result<[u8; P521_SS_LEN], CryptoError> {
let eph = P521PublicKey::from_sec1_bytes(eph_pk)
.map_err(|_| CryptoError::Hybrid("invalid P-521 ephemeral public key".into()))?;
let sk = p521_secret_from_bytes(recipient_secret);
Ok((*sk.diffie_hellman(&eph).raw_secret_bytes()).into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn x448_ecdh_agrees() {
let mut a = [0u8; X448_LEN];
let mut b = [0u8; X448_LEN];
random_bytes(&mut a);
random_bytes(&mut b);
let a_pk = x448_public_from_secret(&a);
let (eph_pk, ss_sender) = x448_encapsulate(&a_pk).unwrap();
let ss_recipient = x448_decapsulate(&a, &eph_pk).unwrap();
assert_eq!(ss_sender, ss_recipient);
let _ = b;
}
#[test]
fn p521_ecdh_agrees() {
let mut a = [0u8; P521_SK_LEN];
random_bytes(&mut a);
let a_pk = p521_public_from_secret(&a).unwrap();
let (eph_pk, ss_sender) = p521_encapsulate(&a_pk).unwrap();
let ss_recipient = p521_decapsulate(&a, &eph_pk).unwrap();
assert_eq!(ss_sender, ss_recipient);
}
#[test]
fn x448_public_is_deterministic() {
let s = [42u8; X448_LEN];
assert_eq!(x448_public_from_secret(&s), x448_public_from_secret(&s));
}
#[test]
fn p521_public_is_deterministic() {
let s = [42u8; P521_SK_LEN];
assert_eq!(
p521_public_from_secret(&s).unwrap(),
p521_public_from_secret(&s).unwrap()
);
}
}