use crate::error::{Error, Result};
use curve25519_dalek::MontgomeryPoint;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SecretKey(pub(crate) [u8; 32]);
#[derive(Clone, PartialEq, Eq)]
pub struct PublicKey(pub(crate) [u8; 32]);
impl SecretKey {
pub(crate) fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl PublicKey {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
#[must_use = "dropping the keypair loses secret key material without zeroization"]
pub fn keygen() -> (PublicKey, SecretKey) {
let mut sk_bytes = [0u8; 32];
super::random::random_bytes(&mut sk_bytes);
let pk = MontgomeryPoint::mul_base_clamped(sk_bytes);
let sk = SecretKey(sk_bytes);
sk_bytes.zeroize();
(PublicKey(pk.to_bytes()), sk)
}
#[must_use = "derived public key must not be discarded"]
pub fn public_from_secret(sk: &SecretKey) -> PublicKey {
PublicKey(MontgomeryPoint::mul_base_clamped(sk.0).to_bytes())
}
pub fn dh(sk: &SecretKey, pk: &PublicKey) -> Result<[u8; 32]> {
let mut shared = MontgomeryPoint(pk.0).mul_clamped(sk.0);
let mut result = shared.to_bytes();
shared.zeroize();
if bool::from(result.ct_eq(&[0u8; 32])) {
result.zeroize();
return Err(Error::DecapsulationFailed);
}
let output = result;
result.zeroize();
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
use hex_literal::hex;
#[test]
fn rfc7748_vector() {
let sk_bytes = hex!("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a");
let expected_pk = hex!("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a");
let sk = SecretKey::from_bytes(sk_bytes);
let pk = public_from_secret(&sk);
assert_eq!(pk.as_bytes(), &expected_pk);
}
#[test]
fn rfc7748_dh_kat() {
let sk_a = SecretKey::from_bytes(hex!(
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"
));
let sk_b = SecretKey::from_bytes(hex!(
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"
));
let pk_b = public_from_secret(&sk_b);
assert_eq!(
pk_b.as_bytes(),
&hex!("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
);
let ss_a = dh(&sk_a, &pk_b).unwrap();
let ss_b = dh(&sk_b, &public_from_secret(&sk_a)).unwrap();
let expected = hex!("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
assert_eq!(ss_a, expected);
assert_eq!(ss_b, expected);
}
#[test]
fn keygen_sizes() {
let (pk, sk) = keygen();
assert!(pk.as_bytes().iter().any(|&b| b != 0));
assert!(sk.as_bytes().iter().any(|&b| b != 0));
}
#[test]
fn public_from_secret_deterministic() {
let (_, sk) = keygen();
let pk1 = public_from_secret(&sk);
let pk2 = public_from_secret(&sk);
assert_eq!(pk1.as_bytes(), pk2.as_bytes());
}
#[test]
fn public_from_secret_matches_keygen() {
let (pk, sk) = keygen();
let pk2 = public_from_secret(&sk);
assert_eq!(pk.as_bytes(), pk2.as_bytes());
}
#[test]
fn dh_agreement() {
let (pk_a, sk_a) = keygen();
let (pk_b, sk_b) = keygen();
let ss_a = dh(&sk_a, &pk_b).unwrap();
let ss_b = dh(&sk_b, &pk_a).unwrap();
assert_eq!(ss_a, ss_b);
}
#[test]
fn dh_low_order_rejected() {
let (_, sk) = keygen();
let zero_pk = PublicKey::from_bytes([0u8; 32]);
assert!(matches!(dh(&sk, &zero_pk), Err(Error::DecapsulationFailed)));
}
#[test]
fn dh_small_order_points() {
let (_, sk) = keygen();
let small_order_points: [[u8; 32]; 5] = [
[0; 32], {
let mut p = [0u8; 32];
p[0] = 1; p
},
{
let mut p = [0xffu8; 32]; p[31] = 0x7f;
p[0] = 0xec;
p
},
{
let mut p = [0xffu8; 32]; p[31] = 0x7f;
p[0] = 0xed;
p
},
{
let mut p = [0xffu8; 32]; p[31] = 0x7f;
p[0] = 0xee;
p
},
];
for point in &small_order_points {
let pk = PublicKey::from_bytes(*point);
assert!(
matches!(dh(&sk, &pk), Err(Error::DecapsulationFailed)),
"small-order point {:02x?} should be rejected",
&point[..4]
);
}
}
}