use borsh::{BorshDeserialize, BorshSerialize};
use ed25519_dalek::{
Signature as Ed25519Signature, Signer as Ed25519Signer, SigningKey, VerifyingKey,
};
use ml_dsa::Signature as MlDsaSignature;
use ml_dsa::SigningKey as MlDsaSigningKey;
use ml_dsa::VerifyingKey as MlDsaVerifyingKey;
use ml_dsa::{
EncodedSignature, EncodedVerifyingKey, KeyExport, KeyInit, Keypair, MlDsa65, Signer, Verifier,
};
use std::fmt;
use zeroize::ZeroizeOnDrop;
#[derive(ZeroizeOnDrop)]
pub struct HybridSigningKey {
#[zeroize(skip)] pub ed25519_sk: SigningKey,
#[zeroize(skip)] pub ml_dsa_sk: Box<MlDsaSigningKey<MlDsa65>>,
}
impl HybridSigningKey {
pub fn generate() -> (Self, HybridVerifyingKey) {
Self::generate_with_provider(&crate::crypto::rng::OsRng)
}
pub fn generate_with_provider<R: crate::crypto::rng::RngProvider + ?Sized>(
provider: &R,
) -> (Self, HybridVerifyingKey) {
let mut ed_seed = [0u8; 32];
provider.fill_bytes(&mut ed_seed);
let ed25519_sk = SigningKey::from_bytes(&ed_seed);
let ed25519_pk = ed25519_sk.verifying_key();
let mut ml_seed_bytes = [0u8; 32];
provider.fill_bytes(&mut ml_seed_bytes);
let ml_dsa_seed = ml_dsa::B32::from(ml_seed_bytes);
let ml_dsa_sk = Box::new(MlDsaSigningKey::<MlDsa65>::new(&ml_dsa_seed));
let ml_dsa_vk = ml_dsa_sk.verifying_key();
let signing_key = Self {
ed25519_sk,
ml_dsa_sk,
};
let verifying_key = HybridVerifyingKey {
ed25519_pk: ed25519_pk.to_bytes(),
ml_dsa_pk: ml_dsa_vk.encode().to_vec(),
};
(signing_key, verifying_key)
}
pub fn pairwise_consistency_check(
&self,
verifying_key: &HybridVerifyingKey,
) -> Result<(), HybridSignError> {
let msg: &[u8] = b"phantom-protocol keygen pairwise-consistency test";
verifying_key.verify(msg, &self.sign(msg))
}
pub fn sign(&self, message: &[u8]) -> HybridSignature {
let ed25519_sig = self.ed25519_sk.sign(message);
let ml_dsa_sig: MlDsaSignature<MlDsa65> = self.ml_dsa_sk.sign(message);
HybridSignature {
ed25519_sig: ed25519_sig.to_bytes(),
ml_dsa_sig: ml_dsa_sig.encode().to_vec(),
}
}
pub fn verifying_key(&self) -> HybridVerifyingKey {
let ed25519_pk = self.ed25519_sk.verifying_key();
HybridVerifyingKey {
ed25519_pk: ed25519_pk.to_bytes(),
ml_dsa_pk: self.ml_dsa_sk.verifying_key().encode().to_vec(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(64);
out.extend_from_slice(&self.ed25519_sk.to_bytes());
out.extend_from_slice(self.ml_dsa_sk.to_bytes().as_slice());
out
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
if bytes.len() != 64 {
return Err(HybridSignError::InvalidKeyLength);
}
let ed25519_bytes: [u8; 32] = bytes[..32]
.try_into()
.map_err(|_| HybridSignError::InvalidKeyFormat)?;
let ed25519_sk = SigningKey::from_bytes(&ed25519_bytes);
let ml_dsa_seed =
ml_dsa::B32::try_from(&bytes[32..]).map_err(|_| HybridSignError::InvalidKeyFormat)?;
let ml_dsa_sk = Box::new(MlDsaSigningKey::<MlDsa65>::new(&ml_dsa_seed));
Ok(Self {
ed25519_sk,
ml_dsa_sk,
})
}
}
impl fmt::Debug for HybridSigningKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HybridSigningKey")
.field("ed25519_sk", &"REDACTED")
.field("ml_dsa_sk", &"REDACTED")
.finish()
}
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
pub struct HybridVerifyingKey {
pub ed25519_pk: [u8; 32],
pub ml_dsa_pk: Vec<u8>,
}
impl HybridVerifyingKey {
pub fn verify(
&self,
message: &[u8],
signature: &HybridSignature,
) -> Result<(), HybridSignError> {
let ed25519_pk = VerifyingKey::from_bytes(&self.ed25519_pk)
.map_err(|_| HybridSignError::InvalidPublicKey)?;
let ed25519_sig = Ed25519Signature::from_bytes(&signature.ed25519_sig);
ed25519_pk
.verify_strict(message, &ed25519_sig)
.map_err(|_| HybridSignError::Ed25519VerificationFailed)?;
let vk_encoded = EncodedVerifyingKey::<MlDsa65>::try_from(self.ml_dsa_pk.as_slice())
.map_err(|_| HybridSignError::InvalidPublicKey)?;
let vk = MlDsaVerifyingKey::<MlDsa65>::decode(&vk_encoded);
let sig_encoded = EncodedSignature::<MlDsa65>::try_from(signature.ml_dsa_sig.as_slice())
.map_err(|_| HybridSignError::InvalidSignature)?;
let sig = MlDsaSignature::<MlDsa65>::decode(&sig_encoded)
.ok_or(HybridSignError::InvalidSignature)?;
vk.verify(message, &sig)
.map_err(|_| HybridSignError::DilithiumVerificationFailed)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(32 + self.ml_dsa_pk.len());
out.extend_from_slice(&self.ed25519_pk);
out.extend_from_slice(&self.ml_dsa_pk);
out
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
const ED_SIZE: usize = 32;
const VK_SIZE: usize = 1952;
if bytes.len() != ED_SIZE + VK_SIZE {
return Err(HybridSignError::InvalidKeyLength);
}
let ed25519_pk: [u8; ED_SIZE] = bytes[..ED_SIZE]
.try_into()
.map_err(|_| HybridSignError::InvalidKeyFormat)?;
let ml_dsa_pk = bytes[ED_SIZE..].to_vec();
Ok(Self {
ed25519_pk,
ml_dsa_pk,
})
}
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct HybridSignature {
pub ed25519_sig: [u8; 64],
pub ml_dsa_sig: Vec<u8>,
}
impl HybridSignature {
pub fn size(&self) -> usize {
64 + self.ml_dsa_sig.len()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(64 + self.ml_dsa_sig.len());
out.extend_from_slice(&self.ed25519_sig);
out.extend_from_slice(&self.ml_dsa_sig);
out
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
if bytes.len() < 64 {
return Err(HybridSignError::InvalidSignatureLength);
}
let ed25519_sig: [u8; 64] = bytes[..64]
.try_into()
.map_err(|_| HybridSignError::InvalidKeyFormat)?;
let ml_dsa_sig = bytes[64..].to_vec();
Ok(Self {
ed25519_sig,
ml_dsa_sig,
})
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
pub enum HybridSignError {
#[error("Invalid key length")]
InvalidKeyLength,
#[error("Invalid key format")]
InvalidKeyFormat,
#[error("Invalid public key")]
InvalidPublicKey,
#[error("Invalid signature")]
InvalidSignature,
#[error("Invalid signature length")]
InvalidSignatureLength,
#[error("Ed25519 verification failed")]
Ed25519VerificationFailed,
#[error("Dilithium verification failed")]
DilithiumVerificationFailed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hybrid_sign_verify() {
let (signing_key, verifying_key) = HybridSigningKey::generate();
let message = b"Hello, post-quantum world!";
let signature = signing_key.sign(message);
assert!(verifying_key.verify(message, &signature).is_ok());
let wrong = b"Wrong message";
assert!(verifying_key.verify(wrong, &signature).is_err());
}
#[test]
fn pairwise_consistency_check_passes_for_a_matched_keypair_and_fails_for_a_mismatch() {
let (sk, vk) = HybridSigningKey::generate();
assert!(sk.pairwise_consistency_check(&vk).is_ok());
let (_other_sk, other_vk) = HybridSigningKey::generate();
assert!(sk.pairwise_consistency_check(&other_vk).is_err());
}
#[test]
fn test_key_serialization() {
let (signing_key, verifying_key) = HybridSigningKey::generate();
let bytes = signing_key.to_bytes();
let restored = HybridSigningKey::from_bytes(&bytes).expect("restore");
let message = b"Test message";
let sig = restored.sign(message);
assert!(verifying_key.verify(message, &sig).is_ok());
let pk_bytes = verifying_key.to_bytes();
let restored_pk = HybridVerifyingKey::from_bytes(&pk_bytes).expect("restore vk");
assert!(restored_pk.verify(message, &sig).is_ok());
}
#[test]
fn test_signature_sizes() {
let (signing_key, _) = HybridSigningKey::generate();
let message = b"Size test";
let signature = signing_key.sign(message);
assert_eq!(signature.ed25519_sig.len(), 64);
assert_eq!(signature.ml_dsa_sig.len(), 3309);
}
}