use ed25519_dalek::{Signer as Ed25519Signer, SigningKey, VerifyingKey};
use ml_dsa::{
Keypair, MlDsa65, Signature, SigningKey as MlDsaSigningKey, Verifier,
VerifyingKey as MlDsaVerifyingKey,
};
use crate::{EndpointKey, Error};
pub const ML_DSA_65_PUBLIC_KEY_BYTES: usize = 1_952;
pub const ML_DSA_65_SIGNATURE_BYTES: usize = 3_309;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EndpointIdentity {
pub classical: EndpointKey,
pub post_quantum: [u8; ML_DSA_65_PUBLIC_KEY_BYTES],
}
impl EndpointIdentity {
#[must_use]
pub const fn new(
classical: EndpointKey,
post_quantum: [u8; ML_DSA_65_PUBLIC_KEY_BYTES],
) -> Self {
Self {
classical,
post_quantum,
}
}
pub fn verify_classical(&self, message: &[u8], signature: &[u8; 64]) -> Result<(), Error> {
let key =
VerifyingKey::from_bytes(self.classical.as_bytes()).map_err(|_| Error::BadSenderKey)?;
key.verify_strict(message, &ed25519_dalek::Signature::from_bytes(signature))
.map_err(|_| Error::BadSignature)
}
pub fn verify_post_quantum(
&self,
message: &[u8],
signature: &[u8; ML_DSA_65_SIGNATURE_BYTES],
) -> Result<(), Error> {
let key_bytes =
ml_dsa::EncodedVerifyingKey::<MlDsa65>::try_from(self.post_quantum.as_slice())
.map_err(|_| Error::BadPostQuantumKey)?;
let key = MlDsaVerifyingKey::<MlDsa65>::decode(&key_bytes);
let signature = Signature::<MlDsa65>::try_from(signature.as_slice())
.map_err(|_| Error::BadPostQuantumSignatureEncoding)?;
key.verify(message, &signature)
.map_err(|_| Error::BadPostQuantumSignature)
}
}
pub struct SigningIdentity {
classical: SigningKey,
post_quantum: MlDsaSigningKey<MlDsa65>,
}
impl SigningIdentity {
#[must_use]
pub fn new(classical: SigningKey, post_quantum: MlDsaSigningKey<MlDsa65>) -> Self {
Self {
classical,
post_quantum,
}
}
#[must_use]
pub fn endpoint(&self) -> EndpointIdentity {
let encoded = self.post_quantum.verifying_key().encode();
let mut post_quantum = [0_u8; ML_DSA_65_PUBLIC_KEY_BYTES];
post_quantum.copy_from_slice(encoded.as_ref());
EndpointIdentity::new(
EndpointKey::from_bytes(self.classical.verifying_key().to_bytes()),
post_quantum,
)
}
}
pub trait EndpointSigner {
fn endpoint(&self) -> EndpointIdentity;
fn sign_classical(&self, message: &[u8]) -> [u8; 64];
fn sign_post_quantum(&self, message: &[u8]) -> [u8; ML_DSA_65_SIGNATURE_BYTES];
}
impl EndpointSigner for SigningIdentity {
fn endpoint(&self) -> EndpointIdentity {
self.endpoint()
}
fn sign_classical(&self, message: &[u8]) -> [u8; 64] {
Ed25519Signer::sign(&self.classical, message).to_bytes()
}
fn sign_post_quantum(&self, message: &[u8]) -> [u8; ML_DSA_65_SIGNATURE_BYTES] {
let encoded = self.post_quantum.sign(message).encode();
let mut signature = [0_u8; ML_DSA_65_SIGNATURE_BYTES];
signature.copy_from_slice(encoded.as_ref());
signature
}
}