phantom_protocol/crypto/
hybrid_sign.rs1use borsh::{BorshDeserialize, BorshSerialize};
12use ed25519_dalek::{
13 Signature as Ed25519Signature, Signer as Ed25519Signer, SigningKey, VerifyingKey,
14};
15use ml_dsa::Signature as MlDsaSignature;
16use ml_dsa::SigningKey as MlDsaSigningKey;
17use ml_dsa::VerifyingKey as MlDsaVerifyingKey;
18use ml_dsa::{
19 EncodedSignature, EncodedVerifyingKey, KeyExport, KeyInit, Keypair, MlDsa65, Signer, Verifier,
20};
21use std::fmt;
22use zeroize::ZeroizeOnDrop;
23
24#[derive(ZeroizeOnDrop)]
39pub struct HybridSigningKey {
40 #[zeroize(skip)] pub ed25519_sk: SigningKey,
42 #[zeroize(skip)] pub ml_dsa_sk: Box<MlDsaSigningKey<MlDsa65>>,
44}
45
46impl HybridSigningKey {
47 pub fn generate() -> (Self, HybridVerifyingKey) {
53 Self::generate_with_provider(&crate::crypto::rng::OsRng)
54 }
55
56 pub fn generate_with_provider<R: crate::crypto::rng::RngProvider + ?Sized>(
72 provider: &R,
73 ) -> (Self, HybridVerifyingKey) {
74 let mut ed_seed = [0u8; 32];
76 provider.fill_bytes(&mut ed_seed);
77 let ed25519_sk = SigningKey::from_bytes(&ed_seed);
78 let ed25519_pk = ed25519_sk.verifying_key();
79
80 let mut ml_seed_bytes = [0u8; 32];
86 provider.fill_bytes(&mut ml_seed_bytes);
87 let ml_dsa_seed = ml_dsa::B32::from(ml_seed_bytes);
88 let ml_dsa_sk = Box::new(MlDsaSigningKey::<MlDsa65>::new(&ml_dsa_seed));
89 let ml_dsa_vk = ml_dsa_sk.verifying_key();
90
91 let signing_key = Self {
92 ed25519_sk,
93 ml_dsa_sk,
94 };
95 let verifying_key = HybridVerifyingKey {
96 ed25519_pk: ed25519_pk.to_bytes(),
97 ml_dsa_pk: ml_dsa_vk.encode().to_vec(),
98 };
99 (signing_key, verifying_key)
100 }
101
102 pub fn pairwise_consistency_check(
116 &self,
117 verifying_key: &HybridVerifyingKey,
118 ) -> Result<(), HybridSignError> {
119 let msg: &[u8] = b"phantom-protocol keygen pairwise-consistency test";
120 verifying_key.verify(msg, &self.sign(msg))
121 }
122
123 pub fn sign(&self, message: &[u8]) -> HybridSignature {
127 let ed25519_sig = self.ed25519_sk.sign(message);
128 let ml_dsa_sig: MlDsaSignature<MlDsa65> = self.ml_dsa_sk.sign(message);
129 HybridSignature {
130 ed25519_sig: ed25519_sig.to_bytes(),
131 ml_dsa_sig: ml_dsa_sig.encode().to_vec(),
132 }
133 }
134
135 pub fn verifying_key(&self) -> HybridVerifyingKey {
136 let ed25519_pk = self.ed25519_sk.verifying_key();
137 HybridVerifyingKey {
138 ed25519_pk: ed25519_pk.to_bytes(),
139 ml_dsa_pk: self.ml_dsa_sk.verifying_key().encode().to_vec(),
140 }
141 }
142
143 pub fn to_bytes(&self) -> Vec<u8> {
148 let mut out = Vec::with_capacity(64);
149 out.extend_from_slice(&self.ed25519_sk.to_bytes());
150 out.extend_from_slice(self.ml_dsa_sk.to_bytes().as_slice());
152 out
153 }
154
155 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
157 if bytes.len() != 64 {
158 return Err(HybridSignError::InvalidKeyLength);
159 }
160 let ed25519_bytes: [u8; 32] = bytes[..32]
161 .try_into()
162 .map_err(|_| HybridSignError::InvalidKeyFormat)?;
163 let ed25519_sk = SigningKey::from_bytes(&ed25519_bytes);
164 let ml_dsa_seed =
165 ml_dsa::B32::try_from(&bytes[32..]).map_err(|_| HybridSignError::InvalidKeyFormat)?;
166 let ml_dsa_sk = Box::new(MlDsaSigningKey::<MlDsa65>::new(&ml_dsa_seed));
170 Ok(Self {
171 ed25519_sk,
172 ml_dsa_sk,
173 })
174 }
175}
176
177impl fmt::Debug for HybridSigningKey {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.debug_struct("HybridSigningKey")
180 .field("ed25519_sk", &"REDACTED")
181 .field("ml_dsa_sk", &"REDACTED")
182 .finish()
183 }
184}
185
186#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
187pub struct HybridVerifyingKey {
188 pub ed25519_pk: [u8; 32],
189 pub ml_dsa_pk: Vec<u8>,
190}
191
192impl HybridVerifyingKey {
193 pub fn verify(
195 &self,
196 message: &[u8],
197 signature: &HybridSignature,
198 ) -> Result<(), HybridSignError> {
199 let ed25519_pk = VerifyingKey::from_bytes(&self.ed25519_pk)
201 .map_err(|_| HybridSignError::InvalidPublicKey)?;
202 let ed25519_sig = Ed25519Signature::from_bytes(&signature.ed25519_sig);
203 ed25519_pk
208 .verify_strict(message, &ed25519_sig)
209 .map_err(|_| HybridSignError::Ed25519VerificationFailed)?;
210
211 let vk_encoded = EncodedVerifyingKey::<MlDsa65>::try_from(self.ml_dsa_pk.as_slice())
213 .map_err(|_| HybridSignError::InvalidPublicKey)?;
214 let vk = MlDsaVerifyingKey::<MlDsa65>::decode(&vk_encoded);
215
216 let sig_encoded = EncodedSignature::<MlDsa65>::try_from(signature.ml_dsa_sig.as_slice())
217 .map_err(|_| HybridSignError::InvalidSignature)?;
218 let sig = MlDsaSignature::<MlDsa65>::decode(&sig_encoded)
219 .ok_or(HybridSignError::InvalidSignature)?;
220
221 vk.verify(message, &sig)
222 .map_err(|_| HybridSignError::DilithiumVerificationFailed)
223 }
224
225 pub fn to_bytes(&self) -> Vec<u8> {
226 let mut out = Vec::with_capacity(32 + self.ml_dsa_pk.len());
227 out.extend_from_slice(&self.ed25519_pk);
228 out.extend_from_slice(&self.ml_dsa_pk);
229 out
230 }
231
232 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
233 const ED_SIZE: usize = 32;
234 const VK_SIZE: usize = 1952;
236 if bytes.len() != ED_SIZE + VK_SIZE {
237 return Err(HybridSignError::InvalidKeyLength);
238 }
239 let ed25519_pk: [u8; ED_SIZE] = bytes[..ED_SIZE]
240 .try_into()
241 .map_err(|_| HybridSignError::InvalidKeyFormat)?;
242 let ml_dsa_pk = bytes[ED_SIZE..].to_vec();
243 Ok(Self {
244 ed25519_pk,
245 ml_dsa_pk,
246 })
247 }
248}
249
250#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
251pub struct HybridSignature {
252 pub ed25519_sig: [u8; 64],
253 pub ml_dsa_sig: Vec<u8>,
254}
255
256impl HybridSignature {
257 pub fn size(&self) -> usize {
258 64 + self.ml_dsa_sig.len()
259 }
260
261 pub fn to_bytes(&self) -> Vec<u8> {
262 let mut out = Vec::with_capacity(64 + self.ml_dsa_sig.len());
263 out.extend_from_slice(&self.ed25519_sig);
264 out.extend_from_slice(&self.ml_dsa_sig);
265 out
266 }
267
268 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HybridSignError> {
269 if bytes.len() < 64 {
270 return Err(HybridSignError::InvalidSignatureLength);
271 }
272 let ed25519_sig: [u8; 64] = bytes[..64]
273 .try_into()
274 .map_err(|_| HybridSignError::InvalidKeyFormat)?;
275 let ml_dsa_sig = bytes[64..].to_vec();
276 Ok(Self {
277 ed25519_sig,
278 ml_dsa_sig,
279 })
280 }
281}
282
283#[derive(Debug, Clone, Copy, thiserror::Error)]
284pub enum HybridSignError {
285 #[error("Invalid key length")]
286 InvalidKeyLength,
287 #[error("Invalid key format")]
288 InvalidKeyFormat,
289 #[error("Invalid public key")]
290 InvalidPublicKey,
291 #[error("Invalid signature")]
292 InvalidSignature,
293 #[error("Invalid signature length")]
294 InvalidSignatureLength,
295 #[error("Ed25519 verification failed")]
296 Ed25519VerificationFailed,
297 #[error("Dilithium verification failed")]
298 DilithiumVerificationFailed,
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_hybrid_sign_verify() {
307 let (signing_key, verifying_key) = HybridSigningKey::generate();
308 let message = b"Hello, post-quantum world!";
309 let signature = signing_key.sign(message);
310
311 assert!(verifying_key.verify(message, &signature).is_ok());
312
313 let wrong = b"Wrong message";
314 assert!(verifying_key.verify(wrong, &signature).is_err());
315 }
316
317 #[test]
318 fn pairwise_consistency_check_passes_for_a_matched_keypair_and_fails_for_a_mismatch() {
319 let (sk, vk) = HybridSigningKey::generate();
320 assert!(sk.pairwise_consistency_check(&vk).is_ok());
322
323 let (_other_sk, other_vk) = HybridSigningKey::generate();
326 assert!(sk.pairwise_consistency_check(&other_vk).is_err());
327 }
328
329 #[test]
330 fn test_key_serialization() {
331 let (signing_key, verifying_key) = HybridSigningKey::generate();
332 let bytes = signing_key.to_bytes();
333 let restored = HybridSigningKey::from_bytes(&bytes).expect("restore");
334
335 let message = b"Test message";
336 let sig = restored.sign(message);
337 assert!(verifying_key.verify(message, &sig).is_ok());
338
339 let pk_bytes = verifying_key.to_bytes();
340 let restored_pk = HybridVerifyingKey::from_bytes(&pk_bytes).expect("restore vk");
341 assert!(restored_pk.verify(message, &sig).is_ok());
342 }
343
344 #[test]
345 fn test_signature_sizes() {
346 let (signing_key, _) = HybridSigningKey::generate();
347 let message = b"Size test";
348 let signature = signing_key.sign(message);
349 assert_eq!(signature.ed25519_sig.len(), 64);
351 assert_eq!(signature.ml_dsa_sig.len(), 3309);
352 }
353}