use hkdf::Hkdf;
use sha2::Sha256;
use crate::error::DataError;
pub const HYBRID_KDF_INFO: &[u8] = b"sunlit-pq-x25519-ml-kem-768/v1";
pub fn combine_shared_secrets(
ml_kem_shared_secret: &[u8],
x25519_shared_secret: &[u8],
) -> Result<[u8; 32], DataError> {
if ml_kem_shared_secret.len() != crate::pq::sizes::ML_KEM_768_SHARED_SECRET_LEN {
return Err(DataError::EnvelopeMalformed {
reason: format!(
"ML-KEM-768 shared secret length mismatch: expected {}, got {}",
crate::pq::sizes::ML_KEM_768_SHARED_SECRET_LEN,
ml_kem_shared_secret.len()
),
});
}
if x25519_shared_secret.len() != crate::pq::sizes::X25519_SHARED_SECRET_LEN {
return Err(DataError::EnvelopeMalformed {
reason: format!(
"X25519 shared secret length mismatch: expected {}, got {}",
crate::pq::sizes::X25519_SHARED_SECRET_LEN,
x25519_shared_secret.len()
),
});
}
let mut ikm = [0u8; 64];
ikm[..32].copy_from_slice(ml_kem_shared_secret);
ikm[32..].copy_from_slice(x25519_shared_secret);
let hk = Hkdf::<Sha256>::new(None, &ikm);
let mut output = [0u8; crate::pq::sizes::HKDF_SHA256_OUTPUT_LEN];
hk.expand(HYBRID_KDF_INFO, &mut output)
.map_err(|_| DataError::EncryptionFailed {
reason: "HKDF-SHA-256 hybrid combiner expansion failed".to_string(),
})?;
Ok(output)
}
pub(crate) fn bind_combiner_id_to_aad(aad: &mut Vec<u8>, combiner_id: u8) {
aad.extend_from_slice(format!(";combiner=0x{combiner_id:02x}").as_bytes());
}