extern crate alloc;
use alloc::{format, vec::Vec};
use ed25519_dalek::{
Signature as Ed25519Signature, Signer, SigningKey as Ed25519SigningKey,
Verifier, VerifyingKey as Ed25519VerifyingKey,
};
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use crate::error::{SigError, SigResult};
use crate::fips204::MlDsa65Keypair;
use crate::types::{
deserialize_bytes_base64url, serialize_bytes_base64url, SigAlgorithm, SigPublicKey, Signature,
};
pub const HYBRID_ALGORITHM: &str = "ed25519+ml_dsa_65";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HybridPublicKey {
#[serde(
serialize_with = "serialize_bytes_base64url",
deserialize_with = "deserialize_bytes_base64url"
)]
pub classical: Vec<u8>,
#[serde(
serialize_with = "serialize_bytes_base64url",
deserialize_with = "deserialize_bytes_base64url"
)]
pub pqc: Vec<u8>,
}
impl HybridPublicKey {
pub fn to_json(&self) -> SigResult<alloc::string::String> {
serde_json::to_string(self).map_err(|e| SigError::Serialization(format!("{}", e)))
}
pub fn from_json(json: &str) -> SigResult<Self> {
serde_json::from_str(json).map_err(|e| SigError::Serialization(format!("{}", e)))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HybridSignature {
#[serde(
serialize_with = "serialize_bytes_base64url",
deserialize_with = "deserialize_bytes_base64url"
)]
pub classical: Vec<u8>,
#[serde(
serialize_with = "serialize_bytes_base64url",
deserialize_with = "deserialize_bytes_base64url"
)]
pub pqc: Vec<u8>,
}
impl HybridSignature {
pub fn to_json(&self) -> SigResult<alloc::string::String> {
serde_json::to_string(self).map_err(|e| SigError::Serialization(format!("{}", e)))
}
pub fn from_json(json: &str) -> SigResult<Self> {
serde_json::from_str(json).map_err(|e| SigError::Serialization(format!("{}", e)))
}
}
pub struct HybridSigner {
ed25519_key: Ed25519SigningKey,
ml_dsa_key: MlDsa65Keypair,
}
impl HybridSigner {
pub fn generate<R: CryptoRng + RngCore>(rng: &mut R) -> SigResult<Self> {
let ed25519_key = Ed25519SigningKey::generate(rng);
let ml_dsa_key = MlDsa65Keypair::generate(rng)?;
Ok(Self { ed25519_key, ml_dsa_key })
}
pub fn public_key(&self) -> HybridPublicKey {
HybridPublicKey {
classical: self.ed25519_key.verifying_key().to_bytes().to_vec(),
pqc: self.ml_dsa_key.public_key().bytes,
}
}
pub fn sign(&self, message: &[u8]) -> SigResult<HybridSignature> {
let classical = self.ed25519_key.sign(message).to_bytes().to_vec();
let pqc = self.ml_dsa_key.sign_deterministic(message)?.bytes;
Ok(HybridSignature { classical, pqc })
}
pub fn verify(message: &[u8], signature: &HybridSignature, public_key: &HybridPublicKey) -> SigResult<()> {
let vk_bytes: [u8; 32] = public_key.classical.as_slice().try_into().map_err(|_| {
SigError::InvalidPublicKey(format!(
"Ed25519 public key must be 32 bytes, got {}",
public_key.classical.len()
))
})?;
let ed25519_vk = Ed25519VerifyingKey::from_bytes(&vk_bytes)
.map_err(|e| SigError::InvalidPublicKey(format!("{}", e)))?;
let sig_bytes: [u8; 64] = signature.classical.as_slice().try_into().map_err(|_| {
SigError::InvalidSignature(format!(
"Ed25519 signature must be 64 bytes, got {}",
signature.classical.len()
))
})?;
let ed25519_sig = Ed25519Signature::from_bytes(&sig_bytes);
ed25519_vk
.verify(message, &ed25519_sig)
.map_err(|_| SigError::HybridClassicalFailed)?;
let pqc_pk = SigPublicKey::new(SigAlgorithm::MlDsa65, public_key.pqc.clone());
let pqc_sig = Signature::new(SigAlgorithm::MlDsa65, signature.pqc.clone());
MlDsa65Keypair::verify(&pqc_pk, message, &pqc_sig).map_err(|_| SigError::HybridPqcFailed)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn both_pass() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let sig = signer.sign(b"hello world").unwrap();
HybridSigner::verify(b"hello world", &sig, &pk).expect("hybrid verify should pass");
}
#[test]
fn classical_only_fails() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let mut sig = signer.sign(b"hello world").unwrap();
sig.classical[0] ^= 0xFF; let err = HybridSigner::verify(b"hello world", &sig, &pk).unwrap_err();
assert_eq!(err, SigError::HybridClassicalFailed);
}
#[test]
fn pqc_only_fails() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let mut sig = signer.sign(b"hello world").unwrap();
sig.pqc[0] ^= 0xFF; let err = HybridSigner::verify(b"hello world", &sig, &pk).unwrap_err();
assert_eq!(err, SigError::HybridPqcFailed);
}
#[test]
fn both_fail() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let mut sig = signer.sign(b"hello world").unwrap();
sig.classical[0] ^= 0xFF;
sig.pqc[0] ^= 0xFF;
let err = HybridSigner::verify(b"hello world", &sig, &pk).unwrap_err();
assert!(err == SigError::HybridClassicalFailed || err == SigError::HybridPqcFailed);
}
#[test]
fn wrong_message_fails() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let sig = signer.sign(b"correct message").unwrap();
assert!(HybridSigner::verify(b"wrong message", &sig, &pk).is_err());
}
#[test]
fn json_round_trip() {
let signer = HybridSigner::generate(&mut OsRng).unwrap();
let pk = signer.public_key();
let sig = signer.sign(b"hello world").unwrap();
let pk2 = HybridPublicKey::from_json(&pk.to_json().unwrap()).unwrap();
let sig2 = HybridSignature::from_json(&sig.to_json().unwrap()).unwrap();
assert_eq!(pk, pk2);
assert_eq!(sig, sig2);
HybridSigner::verify(b"hello world", &sig2, &pk2).expect("round-tripped hybrid verify should pass");
}
}