1use crate::constants::*;
16use crate::error::{Error, Result};
17use crate::wire::{read_header, take, write_header};
18use alloc::boxed::Box;
19use alloc::vec::Vec;
20use ml_dsa::signature::Keypair as _;
21use ml_dsa::{KeyExport as _, MlDsa87};
22use ml_kem::{DecapsulationKey1024, EncapsulationKey1024};
23use sha3::{Digest, Sha3_256};
24use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub struct KeyId([u8; KEY_ID_LEN]);
31
32impl KeyId {
33 pub fn as_bytes(&self) -> &[u8; KEY_ID_LEN] {
35 &self.0
36 }
37}
38
39#[derive(Zeroize, ZeroizeOnDrop)]
41pub(crate) struct Seeds {
42 pub(crate) x25519_sk: [u8; X25519_SK_LEN],
43 pub(crate) mlkem_seed: [u8; MLKEM_SEED_LEN],
44 pub(crate) ed25519_seed: [u8; ED25519_SEED_LEN],
45 pub(crate) mldsa_seed: [u8; MLDSA_SEED_LEN],
46}
47
48pub struct KeyPair {
53 seeds: Seeds,
54 pub(crate) x25519_sk: x25519_dalek::StaticSecret,
55 pub(crate) mlkem_dk: Box<DecapsulationKey1024>,
56 pub(crate) ed25519_sk: ed25519_dalek::SigningKey,
57 pub(crate) mldsa_sk: Box<ml_dsa::SigningKey<MlDsa87>>,
58 public: PublicKeyBundle,
59}
60
61impl KeyPair {
62 pub fn generate() -> Result<Self> {
68 let mut seeds = Seeds {
69 x25519_sk: [0u8; X25519_SK_LEN],
70 mlkem_seed: [0u8; MLKEM_SEED_LEN],
71 ed25519_seed: [0u8; ED25519_SEED_LEN],
72 mldsa_seed: [0u8; MLDSA_SEED_LEN],
73 };
74 getrandom::fill(&mut seeds.x25519_sk).map_err(|_| Error::RandomnessUnavailable)?;
75 getrandom::fill(&mut seeds.mlkem_seed).map_err(|_| Error::RandomnessUnavailable)?;
76 getrandom::fill(&mut seeds.ed25519_seed).map_err(|_| Error::RandomnessUnavailable)?;
77 getrandom::fill(&mut seeds.mldsa_seed).map_err(|_| Error::RandomnessUnavailable)?;
78 Ok(Self::from_seeds(seeds))
79 }
80
81 pub(crate) fn from_seeds(seeds: Seeds) -> Self {
83 let x25519_sk = x25519_dalek::StaticSecret::from(seeds.x25519_sk);
84 let mlkem_dk = Box::new(DecapsulationKey1024::from_seed(seeds.mlkem_seed.into()));
85 let ed25519_sk = ed25519_dalek::SigningKey::from_bytes(&seeds.ed25519_seed);
86 let mldsa_sk = Box::new(ml_dsa::SigningKey::<MlDsa87>::from_seed(
87 &seeds.mldsa_seed.into(),
88 ));
89
90 let public = PublicKeyBundle {
91 x25519: x25519_dalek::PublicKey::from(&x25519_sk),
92 mlkem: Box::new(mlkem_dk.encapsulation_key().clone()),
93 ed25519: ed25519_sk.verifying_key(),
94 mldsa: Box::new(mldsa_sk.verifying_key()),
95 };
96
97 Self {
98 seeds,
99 x25519_sk,
100 mlkem_dk,
101 ed25519_sk,
102 mldsa_sk,
103 public,
104 }
105 }
106
107 pub fn public_keys(&self) -> &PublicKeyBundle {
109 &self.public
110 }
111
112 pub fn to_secret_bytes(&self) -> Zeroizing<Vec<u8>> {
117 let mut out = Vec::with_capacity(SECRET_BUNDLE_LEN);
118 write_header(&mut out, MAGIC_SECRET_BUNDLE);
119 out.extend_from_slice(&self.seeds.x25519_sk);
120 out.extend_from_slice(&self.seeds.mlkem_seed);
121 out.extend_from_slice(&self.seeds.ed25519_seed);
122 out.extend_from_slice(&self.seeds.mldsa_seed);
123 debug_assert_eq!(out.len(), SECRET_BUNDLE_LEN);
124 Zeroizing::new(out)
125 }
126
127 pub fn from_secret_bytes(bytes: &[u8]) -> Result<Self> {
134 let mut rest = read_header(bytes, MAGIC_SECRET_BUNDLE, Error::InvalidKey)?;
135 let seeds = Seeds {
136 x25519_sk: take(&mut rest, Error::InvalidKey)?,
137 mlkem_seed: take(&mut rest, Error::InvalidKey)?,
138 ed25519_seed: take(&mut rest, Error::InvalidKey)?,
139 mldsa_seed: take(&mut rest, Error::InvalidKey)?,
140 };
141 if !rest.is_empty() {
142 return Err(Error::InvalidKey);
143 }
144 Ok(Self::from_seeds(seeds))
145 }
146}
147
148impl core::fmt::Debug for KeyPair {
149 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150 f.debug_struct("KeyPair").finish_non_exhaustive()
151 }
152}
153
154#[derive(Clone)]
159pub struct PublicKeyBundle {
160 pub(crate) x25519: x25519_dalek::PublicKey,
161 pub(crate) mlkem: Box<EncapsulationKey1024>,
162 pub(crate) ed25519: ed25519_dalek::VerifyingKey,
163 pub(crate) mldsa: Box<ml_dsa::VerifyingKey<MlDsa87>>,
164}
165
166impl PublicKeyBundle {
167 pub fn to_bytes(&self) -> Vec<u8> {
169 let mut out = Vec::with_capacity(PUBLIC_BUNDLE_LEN);
170 write_header(&mut out, MAGIC_PUBLIC_BUNDLE);
171 out.extend_from_slice(self.x25519.as_bytes());
172 out.extend_from_slice(&self.mlkem.to_bytes());
173 out.extend_from_slice(self.ed25519.as_bytes());
174 out.extend_from_slice(&self.mldsa.encode());
175 debug_assert_eq!(out.len(), PUBLIC_BUNDLE_LEN);
176 out
177 }
178
179 pub fn key_id(&self) -> KeyId {
181 let digest = Sha3_256::digest(self.to_bytes());
182 let mut id = [0u8; KEY_ID_LEN];
183 id.copy_from_slice(&digest[..KEY_ID_LEN]);
184 KeyId(id)
185 }
186
187 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
202 let mut rest = read_header(bytes, MAGIC_PUBLIC_BUNDLE, Error::InvalidKey)?;
203
204 let x25519_bytes: [u8; X25519_PK_LEN] = take(&mut rest, Error::InvalidKey)?;
205 let mlkem_bytes: [u8; MLKEM1024_EK_LEN] = take(&mut rest, Error::InvalidKey)?;
206 let ed25519_bytes: [u8; ED25519_PK_LEN] = take(&mut rest, Error::InvalidKey)?;
207 let mldsa_bytes: [u8; MLDSA87_VK_LEN] = take(&mut rest, Error::InvalidKey)?;
208 if !rest.is_empty() {
209 return Err(Error::InvalidKey);
210 }
211
212 let x25519 = x25519_dalek::PublicKey::from(x25519_bytes);
213 let mlkem =
214 EncapsulationKey1024::new(&mlkem_bytes.into()).map_err(|_| Error::InvalidKey)?;
215 let ed25519 = ed25519_dalek::VerifyingKey::from_bytes(&ed25519_bytes)
216 .map_err(|_| Error::InvalidKey)?;
217 let mldsa = ml_dsa::VerifyingKey::<MlDsa87>::decode(&mldsa_bytes.into());
218
219 Ok(Self {
220 x25519,
221 mlkem: Box::new(mlkem),
222 ed25519,
223 mldsa: Box::new(mldsa),
224 })
225 }
226}
227
228impl core::fmt::Debug for PublicKeyBundle {
229 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
230 f.debug_struct("PublicKeyBundle")
231 .field("x25519", &self.x25519)
232 .finish_non_exhaustive()
233 }
234}
235
236impl PartialEq for PublicKeyBundle {
237 fn eq(&self, other: &Self) -> bool {
238 self.to_bytes() == other.to_bytes()
239 }
240}
241
242impl Eq for PublicKeyBundle {}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn constants_match_crate_types() {
250 use ml_dsa::signature::SignatureEncoding as _;
251 let kp = KeyPair::generate().unwrap();
252 assert_eq!(kp.public.mlkem.to_bytes().len(), MLKEM1024_EK_LEN);
253 assert_eq!(kp.public.mldsa.encode().len(), MLDSA87_VK_LEN);
254 use ed25519_dalek::Signer as _;
255 use ml_dsa::signature::Signer as _;
256 let ed_sig = kp.ed25519_sk.sign(b"x");
257 assert_eq!(ed_sig.to_bytes().len(), ED25519_SIG_LEN);
258 let pq_sig: ml_dsa::Signature<MlDsa87> = kp.mldsa_sk.sign(b"x");
259 assert_eq!(pq_sig.to_bytes().len(), MLDSA87_SIG_LEN);
260 }
261
262 #[test]
263 fn public_bundle_roundtrip() {
264 let kp = KeyPair::generate().unwrap();
265 let bytes = kp.public_keys().to_bytes();
266 assert_eq!(bytes.len(), PUBLIC_BUNDLE_LEN);
267 let parsed = PublicKeyBundle::from_bytes(&bytes).unwrap();
268 assert_eq!(parsed, *kp.public_keys());
269 }
270
271 #[test]
272 fn secret_bundle_roundtrip() {
273 let kp = KeyPair::generate().unwrap();
274 let secret = kp.to_secret_bytes();
275 assert_eq!(secret.len(), SECRET_BUNDLE_LEN);
276 let restored = KeyPair::from_secret_bytes(&secret).unwrap();
277 assert_eq!(restored.public_keys(), kp.public_keys());
278 }
279
280 #[test]
281 fn secret_bundle_rejects_bad_input() {
282 let kp = KeyPair::generate().unwrap();
283 let secret = kp.to_secret_bytes();
284 assert_eq!(
286 KeyPair::from_secret_bytes(&secret[..secret.len() - 1]).unwrap_err(),
287 Error::InvalidKey
288 );
289 let mut long = secret.to_vec();
291 long.push(0);
292 assert_eq!(
293 KeyPair::from_secret_bytes(&long).unwrap_err(),
294 Error::InvalidKey
295 );
296 assert_eq!(
298 KeyPair::from_secret_bytes(&kp.public_keys().to_bytes()).unwrap_err(),
299 Error::InvalidKey
300 );
301 }
302
303 #[test]
304 fn public_bundle_rejects_invalid_ed25519_point() {
305 let invalid = (0u8..=255)
309 .map(|b| {
310 let mut k = [b; ED25519_PK_LEN];
311 k[0] = b.wrapping_add(1);
312 k
313 })
314 .find(|k| ed25519_dalek::VerifyingKey::from_bytes(k).is_err())
315 .expect("some encoding must fail decompression");
316
317 let kp = KeyPair::generate().unwrap();
318 let mut bytes = kp.public_keys().to_bytes();
319 let off = HEADER_LEN + X25519_PK_LEN + MLKEM1024_EK_LEN;
320 bytes[off..off + ED25519_PK_LEN].copy_from_slice(&invalid);
321 assert_eq!(
322 PublicKeyBundle::from_bytes(&bytes).unwrap_err(),
323 Error::InvalidKey
324 );
325 }
326
327 #[test]
328 fn public_bundle_rejects_invalid_mlkem_key() {
329 let kp = KeyPair::generate().unwrap();
330 let mut bytes = kp.public_keys().to_bytes();
331 let off = HEADER_LEN + X25519_PK_LEN;
334 bytes[off..off + MLKEM1024_EK_LEN].fill(0xFF);
335 assert_eq!(
336 PublicKeyBundle::from_bytes(&bytes).unwrap_err(),
337 Error::InvalidKey
338 );
339 }
340
341 #[test]
342 fn debug_redacts_secrets() {
343 let kp = KeyPair::generate().unwrap();
344 let dbg = format!("{kp:?}");
345 assert_eq!(dbg, "KeyPair { .. }");
346 }
347}