1mod cert;
12
13use crate::pem;
14use crate::{eddsa, mldsa};
15use base64::Engine;
16use base64::engine::general_purpose::STANDARD as BASE64;
17use der::asn1::BitStringRef;
18use der::{AnyRef, Decode, Encode};
19use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
20use sha2::Digest;
21use spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo};
22use std::error::Error;
23
24const SIGNATURE_PREFIX: &[u8] = b"CompositeAlgorithmSignatures2025";
27
28pub const SIGNATURE_DOMAIN: &[u8] = b"COMPSIG-MLDSA65-Ed25519-SHA512";
30
31pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.6.48");
33
34pub const SECRET_KEY_SIZE: usize = 64;
37
38pub const PUBLIC_KEY_SIZE: usize = 1984;
41
42pub const SIGNATURE_SIZE: usize = 3373;
45
46pub const FINGERPRINT_SIZE: usize = 32;
48
49#[derive(Clone)]
52pub struct SecretKey {
53 ml_key: mldsa::SecretKey,
54 ed_key: eddsa::SecretKey,
55}
56
57impl SecretKey {
58 pub fn generate() -> SecretKey {
60 SecretKey {
61 ml_key: mldsa::SecretKey::generate(),
62 ed_key: eddsa::SecretKey::generate(),
63 }
64 }
65
66 pub fn compose(ml_key: mldsa::SecretKey, ed_key: eddsa::SecretKey) -> Self {
69 Self { ml_key, ed_key }
70 }
71
72 pub fn split(self) -> (mldsa::SecretKey, eddsa::SecretKey) {
75 (self.ml_key, self.ed_key)
76 }
77
78 pub fn from_bytes(seed: &[u8; SECRET_KEY_SIZE]) -> Self {
80 let ml_seed: [u8; 32] = seed[..32].try_into().unwrap();
81 let ed_seed: [u8; 32] = seed[32..].try_into().unwrap();
82
83 Self {
84 ml_key: mldsa::SecretKey::from_bytes(&ml_seed),
85 ed_key: eddsa::SecretKey::from_bytes(&ed_seed),
86 }
87 }
88
89 pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
91 let info = pkcs8::PrivateKeyInfo::from_der(der)?;
93
94 if info.encoded_len()?.try_into() != Ok(der.len()) {
96 return Err("trailing data in private key".into());
97 }
98 if info.algorithm.oid != OID {
100 return Err("not a composite ML-DSA-65-Ed25519-SHA512 private key".into());
101 }
102 let seed: [u8; 64] = info
104 .private_key
105 .try_into()
106 .map_err(|_| "composite private key must be 64 bytes")?;
107
108 Ok(Self::from_bytes(&seed))
109 }
110
111 pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
113 let (kind, data) = pem::decode(pem_str.as_bytes())?;
115 if kind != "PRIVATE KEY" {
116 return Err(format!("invalid PEM tag {}", kind).into());
117 }
118 Self::from_der(&data)
120 }
121
122 pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE] {
124 let mut out = [0u8; 64];
125 out[..32].copy_from_slice(&self.ml_key.to_bytes());
126 out[32..].copy_from_slice(&self.ed_key.to_bytes());
127 out
128 }
129
130 pub fn to_der(&self) -> Vec<u8> {
132 let alg = pkcs8::AlgorithmIdentifierRef {
135 oid: OID,
136 parameters: None,
137 };
138 let key_bytes = self.to_bytes();
140
141 let info = pkcs8::PrivateKeyInfo {
142 algorithm: alg,
143 private_key: &key_bytes,
144 public_key: None,
145 };
146 info.to_der().unwrap()
147 }
148
149 pub fn to_pem(&self) -> String {
151 pem::encode("PRIVATE KEY", &self.to_der())
152 }
153
154 pub fn public_key(&self) -> PublicKey {
156 PublicKey {
157 ml_key: self.ml_key.public_key(),
158 ed_key: self.ed_key.public_key(),
159 }
160 }
161
162 pub fn fingerprint(&self) -> Fingerprint {
164 self.public_key().fingerprint()
165 }
166
167 pub fn sign(&self, message: &[u8]) -> Signature {
169 let m_prime = split_signing_message(message);
170
171 let ml_sig = self.ml_key.sign(&m_prime, SIGNATURE_DOMAIN);
173 let ed_sig = self.ed_key.sign(&m_prime);
174
175 Signature::compose(ml_sig, ed_sig)
176 }
177}
178
179#[derive(Debug, Clone)]
182pub struct PublicKey {
183 ml_key: mldsa::PublicKey,
184 ed_key: eddsa::PublicKey,
185}
186
187impl PublicKey {
188 pub fn compose(ml_key: mldsa::PublicKey, ed_key: eddsa::PublicKey) -> Self {
191 Self { ml_key, ed_key }
192 }
193
194 pub fn split(self) -> (mldsa::PublicKey, eddsa::PublicKey) {
197 (self.ml_key, self.ed_key)
198 }
199
200 pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE]) -> Result<Self, Box<dyn Error>> {
202 let ml_bytes: [u8; 1952] = bytes[..1952].try_into().unwrap();
203 let ed_bytes: [u8; 32] = bytes[1952..].try_into().unwrap();
204
205 Ok(Self {
206 ml_key: mldsa::PublicKey::from_bytes(&ml_bytes),
207 ed_key: eddsa::PublicKey::from_bytes(&ed_bytes)?,
208 })
209 }
210
211 pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
213 let info: SubjectPublicKeyInfo<AlgorithmIdentifier<AnyRef>, BitStringRef> =
215 SubjectPublicKeyInfo::from_der(der)?;
216
217 if info.encoded_len()?.try_into() != Ok(der.len()) {
219 return Err("trailing data in public key".into());
220 }
221 if info.algorithm.oid != OID {
223 return Err("not a composite ML-DSA-65-Ed25519-SHA512 public key".into());
224 }
225 let key_bytes: [u8; 1984] = info
227 .subject_public_key
228 .as_bytes()
229 .ok_or("invalid public key bit string")?
230 .try_into()
231 .map_err(|_| "composite public key must be 1984 bytes")?;
232
233 Self::from_bytes(&key_bytes)
234 }
235
236 pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
238 let (kind, data) = pem::decode(pem_str.as_bytes())?;
240 if kind != "PUBLIC KEY" {
241 return Err(format!("invalid PEM tag {}", kind).into());
242 }
243 Self::from_der(&data)
245 }
246
247 pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
249 let mut out = [0u8; 1984];
250 out[..1952].copy_from_slice(&self.ml_key.to_bytes());
251 out[1952..].copy_from_slice(&self.ed_key.to_bytes());
252 out
253 }
254
255 pub fn to_der(&self) -> Vec<u8> {
257 let alg = spki::AlgorithmIdentifierRef {
260 oid: OID,
261 parameters: None,
262 };
263 let key_bytes = self.to_bytes();
265
266 let info = SubjectPublicKeyInfo::<AnyRef, BitStringRef> {
267 algorithm: alg,
268 subject_public_key: BitStringRef::from_bytes(&key_bytes).unwrap(),
269 };
270 info.to_der().unwrap()
271 }
272
273 pub fn to_pem(&self) -> String {
275 pem::encode("PUBLIC KEY", &self.to_der())
276 }
277
278 pub fn fingerprint(&self) -> Fingerprint {
280 let mut hasher = sha2::Sha256::new();
281 hasher.update(self.ml_key.to_bytes());
282 hasher.update(self.ed_key.to_bytes());
283 Fingerprint(hasher.finalize().into())
284 }
285
286 pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), Box<dyn Error>> {
288 let mut hasher = sha2::Sha512::new();
291 hasher.update(message);
292 let prehash: [u8; 64] = hasher.finalize().into();
293
294 let mut m_prime =
295 Vec::with_capacity(SIGNATURE_PREFIX.len() + SIGNATURE_DOMAIN.len() + 1 + 64);
296 m_prime.extend_from_slice(SIGNATURE_PREFIX);
297 m_prime.extend_from_slice(SIGNATURE_DOMAIN);
298 m_prime.push(0); m_prime.extend_from_slice(&prehash);
300
301 let (ml_sig, ed_sig) = signature.split();
303
304 self.ml_key.verify(&m_prime, SIGNATURE_DOMAIN, &ml_sig)?;
305 self.ed_key.verify(&m_prime, &ed_sig)?;
306
307 Ok(())
308 }
309}
310
311impl Serialize for PublicKey {
312 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
313 serializer.serialize_str(&BASE64.encode(self.to_bytes()))
314 }
315}
316
317impl<'de> Deserialize<'de> for PublicKey {
318 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
319 let s = String::deserialize(deserializer)?;
320 let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
321 let arr: [u8; PUBLIC_KEY_SIZE] = bytes
322 .try_into()
323 .map_err(|_| de::Error::custom("invalid public key length"))?;
324 PublicKey::from_bytes(&arr).map_err(de::Error::custom)
325 }
326}
327
328#[cfg(feature = "cbor")]
329impl crate::cbor::Encode for PublicKey {
330 fn encode_cbor(&self) -> Vec<u8> {
331 self.to_bytes().encode_cbor()
332 }
333}
334
335#[cfg(feature = "cbor")]
336impl crate::cbor::Decode for PublicKey {
337 fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
338 let bytes = <[u8; PUBLIC_KEY_SIZE]>::decode_cbor(data)?;
339 Self::from_bytes(&bytes).map_err(|e| crate::cbor::Error::DecodeFailed(e.to_string()))
340 }
341
342 fn decode_cbor_notrail(
343 decoder: &mut crate::cbor::Decoder<'_>,
344 ) -> Result<Self, crate::cbor::Error> {
345 let bytes = decoder.decode_bytes_fixed::<PUBLIC_KEY_SIZE>()?;
346 Self::from_bytes(&bytes).map_err(|e| crate::cbor::Error::DecodeFailed(e.to_string()))
347 }
348}
349
350pub fn split_signing_message(message: &[u8]) -> Vec<u8> {
359 let mut hasher = sha2::Sha512::new();
360 hasher.update(message);
361 let prehash: [u8; 64] = hasher.finalize().into();
362
363 let mut m_prime = Vec::with_capacity(SIGNATURE_PREFIX.len() + SIGNATURE_DOMAIN.len() + 1 + 64);
364 m_prime.extend_from_slice(SIGNATURE_PREFIX);
365 m_prime.extend_from_slice(SIGNATURE_DOMAIN);
366 m_prime.push(0); m_prime.extend_from_slice(&prehash);
368 m_prime
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
373pub struct Signature {
374 ml_sig: mldsa::Signature,
375 ed_sig: eddsa::Signature,
376}
377
378impl Signature {
379 pub fn compose(ml_sig: mldsa::Signature, ed_sig: eddsa::Signature) -> Self {
382 Self { ml_sig, ed_sig }
383 }
384
385 pub fn split(&self) -> (mldsa::Signature, eddsa::Signature) {
388 (self.ml_sig.clone(), self.ed_sig)
389 }
390
391 pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Self {
393 let ml_bytes: [u8; 3309] = bytes[..3309].try_into().unwrap();
394 let ed_bytes: [u8; 64] = bytes[3309..].try_into().unwrap();
395
396 Self {
397 ml_sig: mldsa::Signature::from_bytes(&ml_bytes),
398 ed_sig: eddsa::Signature::from_bytes(&ed_bytes),
399 }
400 }
401
402 pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
404 let mut out = [0u8; SIGNATURE_SIZE];
405 out[..3309].copy_from_slice(&self.ml_sig.to_bytes());
406 out[3309..].copy_from_slice(&self.ed_sig.to_bytes());
407 out
408 }
409}
410
411impl Serialize for Signature {
412 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
413 serializer.serialize_str(&BASE64.encode(self.to_bytes()))
414 }
415}
416
417impl<'de> Deserialize<'de> for Signature {
418 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
419 let s = String::deserialize(deserializer)?;
420 let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
421 let arr: [u8; SIGNATURE_SIZE] = bytes
422 .try_into()
423 .map_err(|_| de::Error::custom("invalid signature length"))?;
424 Ok(Signature::from_bytes(&arr))
425 }
426}
427
428#[cfg(feature = "cbor")]
429impl crate::cbor::Encode for Signature {
430 fn encode_cbor(&self) -> Vec<u8> {
431 self.to_bytes().encode_cbor()
432 }
433}
434
435#[cfg(feature = "cbor")]
436impl crate::cbor::Decode for Signature {
437 fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
438 let bytes = <[u8; SIGNATURE_SIZE]>::decode_cbor(data)?;
439 Ok(Self::from_bytes(&bytes))
440 }
441
442 fn decode_cbor_notrail(
443 decoder: &mut crate::cbor::Decoder<'_>,
444 ) -> Result<Self, crate::cbor::Error> {
445 let bytes = decoder.decode_bytes_fixed::<SIGNATURE_SIZE>()?;
446 Ok(Self::from_bytes(&bytes))
447 }
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub struct Fingerprint([u8; FINGERPRINT_SIZE]);
453
454impl Fingerprint {
455 pub fn from_bytes(bytes: &[u8; FINGERPRINT_SIZE]) -> Self {
457 Self(*bytes)
458 }
459
460 pub fn to_bytes(&self) -> [u8; FINGERPRINT_SIZE] {
462 self.0
463 }
464}
465
466impl Serialize for Fingerprint {
467 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
468 serializer.serialize_str(&BASE64.encode(self.to_bytes()))
469 }
470}
471
472impl<'de> Deserialize<'de> for Fingerprint {
473 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
474 let s = String::deserialize(deserializer)?;
475 let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
476 let arr: [u8; FINGERPRINT_SIZE] = bytes
477 .try_into()
478 .map_err(|_| de::Error::custom("invalid fingerprint length"))?;
479 Ok(Fingerprint::from_bytes(&arr))
480 }
481}
482
483#[cfg(feature = "cbor")]
484impl crate::cbor::Encode for Fingerprint {
485 fn encode_cbor(&self) -> Vec<u8> {
486 self.to_bytes().encode_cbor()
487 }
488}
489
490#[cfg(feature = "cbor")]
491impl crate::cbor::Decode for Fingerprint {
492 fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
493 let bytes = <[u8; FINGERPRINT_SIZE]>::decode_cbor(data)?;
494 Ok(Self::from_bytes(&bytes))
495 }
496
497 fn decode_cbor_notrail(
498 decoder: &mut crate::cbor::Decoder<'_>,
499 ) -> Result<Self, crate::cbor::Error> {
500 let bytes = decoder.decode_bytes_fixed::<FINGERPRINT_SIZE>()?;
501 Ok(Self::from_bytes(&bytes))
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 mod ietf_vectors {
512 pub const TEST_SECKEY: &str = "\
513d79b8541af185b7bb61ea99c302df11d2b760cb8aa1d0628be8882a82d87\
514589c84210cb6f19c6ebb32992b59440eb7f4d5c75d50ebe1c18f55ed16e8\
515565e3529";
516
517 pub const TEST_SECKEY_PKCS8: &str = "\
5183051020100300a06082b060105050706300440d79b8541af185b7bb61ea9\
5199c302df11d2b760cb8aa1d0628be8882a82d87589c84210cb6f19c6ebb32\
520992b59440eb7f4d5c75d50ebe1c18f55ed16e8565e3529";
521
522 pub const TEST_PUBKEY: &str = "\
523a22f268082309f80fecf71f1dcc699e1d0be28d8d66a07fe7c8b75922e5a\
524200254b56376d3be068859929674b34b9bbebbf5fc5a295d2fd2a41b0d33\
525a8444b90a31deb699f41a8d7787d0ca5f42c2b054411ed71e732b60ab5f7\
52658e80c19323e4f7aa734e39755683dc5e864df71e18151bba49e1e8fed11\
5270d9149df05a46938d3e2e33b2391df6d8580e9c395f3bedd9277c2bf6b1b\
52877e669de20ba1b5434eff5c2bc9b58d54b26cc4580e98bd07e2b5fa0ed80\
5290a811a758ff89f63aff652b9488ea064862dc220ed0bc8104535d32ea0a4\
53062f033bb34b7031cfff70f2513c294c7c72959980d97244365988d37aefd\
531a8901c4d77ebf462bba17fab6feb68d8cfbf7e3aaa08cd2544b1fc6d95e9\
5323f51a87cdbf908ad9ec058070b80f0f5d32b40a912ffbc504d401921e247\
533be6a72c2148f67932dacb3b309e27aac2d6370b558980e6ec5aa1b771c81\
534f038b0ecbbc9065ec256b243e40c178655fd1864ea72a09244264c48e6e2\
535f15363826a33a8f350f5685c276145a296901d3bc67a8adba0bab5374bd0\
5360d8a96350943565a65e383ae5e4deb3580a2a0b3330ada29679e90b648cc\
5371585aeffb8d1a7df77760f9bd16c20ee1fd6076d394be1af5d927e123c4b\
538d07065c14c11f1c1b1d3d215601345aeb75d7a7bac91526efd2a65b75c08\
5395c37de26d22570a0852c3aa55d5387a93777390c6487bcbd8dcc743f66d1\
540bc65ead105a08905e3ccb49072e06f9bd60fea721f52a80f399a94c89515\
5410fa71ba5a00cec39abd51f6423bb04dcc232d86f9f251c25e82e1304d812\
542963d3378c5778fc26ad7cef6dc168037dae0c1eb2d079fc6e97e639b3207\
54384ded4b36547ec7cbac8b04b69189b80ce958897dff748af1602f1fa3e03\
544f6e61b84816b2017222bb96de2a3e6649cd84a4a52d1ed7dde44701ce304\
5457134bb9403f2fb546af1101ffeda8d0318c1c87df1a4dbcfd821336ffb9e\
5461e3c707695b6dfe6173caa17e5e6b4a69e8f2334be2c5c033887f17c0135\
547c2ee3e70b88dcfda85e6ed8ee4603d3f45b620dd819d15f2821f5b0d9ee9\
54828d6d76bd799dd7a5dd7a98392a174af6a45274ea99d444884bf26744c15\
5499c9a609d136c1348906a095da0b5fa696575ea4712e17bbbe1b003cd3254\
550e93fd9325ee7d5e22fb34ae3b075558275bccce9d50a32bd62e8b78c7b6c\
551f0563a488ff5c456229881facc237bc365351f8f6c7094e6fc608f7937bb\
5527dbf6ddc57bc9f6e817db3fb9b998fe2a9b17131cf412bc5e75044620573\
553cbc53b4215181553717e0952a81bc0b520cd4b4a1d00750b550b372ecb16\
554113cb451fd6799c3d45c8c70e8209d41259369770389aa50c6689ad30263\
55501126fb8c3c34ffc04bea31f44e1ee98be0cfa70d8ee42c0497bf9194f3f\
556edf078520195b30e84540f0ff73b92d9dea48db5315a0db57cd10c0f53da\
557989245a3cffc593cbacf8063283b23b22b2a10bfc4b4b250b7388ddaa014\
5584d960c3f738cc10342133bf3d0f0e0a6179554b04ab0119e1b3111818065\
559cbc1b0b64cdf26b2f0c74f985e17d0e4f871a34c487bff2097f5f848b29b\
5608d50bb86e64b30f0031bb14ee6986a4b17be781d9dc0afdf6075365b6951\
561ec211e55ba5df0a484ffcffd33cade8394dbba38c2f4017cc1eb6f3f16b0\
56241fe1a53ac5427d5b5071f5ee37fd1bb57722985ef4510faca5e457666a7\
563c00cb15727401f5898ae2a5fa2da6c60813b0903fbdcf521b92ad2fa4b87\
564361e344a1998963c536691caecd3bea1028bfcec0a0b9738976f77631405\
565f3b351893b55a1c9138abd33d7432cc1f80bd403a1655e341af8b02e9890\
566edcdcae965ba31991ec8bcb9ca5ae1359ca208835a469ff0f4678ccd3680\
5679770b7ecef4a479431ba10fd19fce57d99e70ab77fff245a531b2225de01\
568341d4f10d96d46171c4ba59003fea32121c9ec60ac33247c4cc21b32a117\
5698f3a3769fee54bb6d6a88c3a2fedbbb4bcdd899bc396d89e8c2556c3562c\
570af606468265ae58c823125402e7e8046b94c3c6412c89410376d4804bae4\
5718004688e7541e33ac98e23ea0d1851e65a41694e8e38a7901f713e13945a\
572c8b8adf75964ec284e2713747342fb0cefa2b48e1f80c993f34c4384b967\
57302241c3a4138744eee23f69b2bfcfe9d20aa615b0dd23e944eb423133a8a\
574b3cbb5bc9978d54544a7c1caa501da9e390d18f04771c334f1c689e20052\
5754880644863b4c21295bebe59fe6b940c2e0112e0f8731e6fc7ab60914835\
576d53dd82339be25551b430edadc20ed9927f29c1cb6a8ef278527fd05e982\
5771037bacf11888f6a63cb2ef82fd6635f3bc83bca351ac9f6bb6e5bef2f9e\
578a15436e2b9b5506dfdfc82de2b20dc093e955bfc305e870bd040c1d2d8ff\
579463847c1ffa09e7e21a6c9c4517702d23281037238675eff574a2dd420ca\
5800416a0ec8c5d7c596713df2b2352b5976f6381f4f88fcc8ea4fb47fc3287\
5814bdabf1637545dc6fa9ba8fa04156dd225debf9899e065f48275c51832bf\
582e70483fe6303f5f1854f5bfc99fe5828a0ae414e24e75b7abaab17db01b9\
583ebf3c067396eb161ee168ce6a7620b3be3def3ee2ea6e74e99f76f56cab0\
5844384b1462d251d0db511d2caf73bfb49fb05a95dfc9756ae3b61b2ef6f95\
58531933a016417e0f82fb05dfc9f53a89b29f64b8844757bb320d80be22c2d\
586117b95dea731f26dc933b9c6812d5b8a4c720df37682e53da2d9ef0605a6\
5878683c182a56a108000f87532623a8c35967c549a4893c5304e6b00059abc\
588a5a0339ce946319a92ae0d18d2a98659605081c8a3c1d4afa788c6d971f2\
5891a90c207";
590 pub const TEST_MSG: &str = "\
591The quick brown fox jumps over the lazy dog.";
592
593 pub const TEST_SIG: &str = "\
59412bcef243d7f459a3350bce36b4aac5195ccebcffd770f9e4222c7fade93\
595f462f0e6a1d128bd23d5526b967008db4501f5ae2fcf24cdac114ee479d6\
5969ae79ce2aaf6109109aa8fe04cfa0c25a1a5cba1ece496185d4af532e75e\
59764d63427fb2e18c442d35c899e7ede69743b9dc034838bd6729a48b54fab\
598f4253c2cd0f667b30c03521d551bfe7ae1693f992842f0ef3a9093cee7a7\
599b6e409279b414d32422543e1db594924dcfe2e67d20bdfb751a874a1bb8b\
6003374372f26ccf31c69400fed3768b886867157f913468708e20354b7bbec\
60165b61dd5ac4f91cbc1cbb8616343cf8e084abffc2a7e129a929fcfaba430\
602619eb0036fe90d58baa7c06bc4e4ecaa730861564eb6337e964575652b01\
603ce15c074157dc66cc019ccc1d795a0a18579f81e865d0be73907b71dc40a\
60485a5d144d375d09a0cdde5caaa2dd869887896256813731d13e997f3021b\
605c0d67f9dfc2722a8e5f81537021e40adc9226a9260d6049825c9b2c79a14\
6063308cfdc7067e20696354eca8cb9ae1b77d24672189f05b1921936d31e12\
607f0808dbde49d8c8c5829010bbc854fa9d2d6f5587ee61b31e6b4287b8952\
608d9c731f056ffe596472899f366621c5450e2b9bc794c0d6666ae696271b4\
60962641403726e746b03a7c25aefec434141200eea151ca3037aaf14d84fc2\
610ce5d4c988079be85905da052258d248b36e07cbb4caa248be0f39c33ff21\
6114f6c1c8b28ff00a71322b4ee9519f2979aded2e61695b55dc9b6912d9fc5\
6121edd84561eb4621d3b93addaaad7a888b74c945dd215242d51dd8534784b\
61319c8eafded273632b4bdbbd50a2720b7b65a13d5af09921ed5ee8b16d85d\
614826172db5af6714df20ed3bf9d22ceaeab3d68005d97b1b89f2e1f0ce39d\
6155ce83327c2306fb4531baa507623ee869fc6b023bdecae705c45f604f56c\
6165384a01017f09c47acb6bdb8d41114fc4ff7617cb781190cb3521164c309\
6172467097d3d41a9bb4ecc85d1ab604daa3322e4f1d48bf44f7b016b90ae77\
6181ed1ea4f155e58a47e42001c529595b3d3e21641a22e1d0e386f6e5b96cb\
6196d1b74ac4d462883a0dd7001af58d4e78ab3d18991d044e4ef6e670445df\
620bad8d4a86fe78edaa5bdd6a57c9daa484ffd6fa7281e92a64c74d90de38f\
62133f4fa001780df159eef9774c50bf6790c6db3444c3871da1c27c8d09145\
622697b27bd73f03fabe77842d17abe014f8e1194e27a0944af9b0719d4a11a\
6235fcf560a5a27503ad9cddb36750b558ee90d910089e18c6a9a6b460917c3\
624e5f37ecf914237c567f9d91ee17c800f00bf449545565a0ea6f79fe700bb\
625f0e978a2d582d4d7812763cad8e165cbc0ff39ab016e70988d1a70c74ec7\
6267fa8701725148078931599f8ea95c964f5a9770814397ec3273df3c282f9\
6271c05974c7716a5c02c4a116303f59e9cb2e986a9f8793685b7e5aedc983d\
62843776cfadeeb2867333bbd3cde86461985f8be1612c7495a4b94387d8ec4\
6296ad8808ecd1922e0e67e616e3b374d8e644674ad88a80c2d207facc47ca5\
630668cf91967c4adaeb453d48fa6cad2a8ada3206a465116486fdb8d518784\
631e36d88836065807152dbb6fac7edf6d2be9fb6838a6ce4aee789d24aae89\
6325b42b0b03323e98f54a497dc965c143d108b7854d172b01da36524821150\
633351eb0b55a4be62afc9524540cfcea88162896d1826f03e11356fa216170\
634b1f47ea84a3cd3a1a135524d32a8a24069ff6181e84ff13096768fccc3f2\
635d98038106b416ee0d00109d0e85834b5f4f06a08c0891d2b17cf1d4bfe74\
636e2a5336da9fa8d8293fe1a9d46e79ef2267f48df18bb14cef525e1b39a7c\
637b3c3e8b973ed15f6ac088850e12f9b532822e6c51c75a691897a63d48476\
6381f18846db014045f6cff76628c975a0ffbfeb7242032092610bc5c68249b\
639799629a7de8c9441d5f5fc0a413edb1d5f61679ff58d9df47596ebb0eb66\
640cf1e68173233a179b3f90e3bab4f8c812e584e6259110c759c951fa8e92a\
641e82092e6c9f058d3c6f958abb07784f128aae38dae47646bdfb141d58480\
6422fa3e6ccaf569e0e0cd9d563ece2fa1c33c17092fc9ca997d05221224328\
643f4697ec17c8ce771328d09d159044e07ec9b4b3f559b3048b646b7a4fd38\
6449e0d26857af0f8f8a2554cc5572a951933bfbda8dda69bbc66b85cbddd01\
6452f30bd268799848f6c5521336ece4ec917778edafb78e2c5ae901b7dc48d\
646db0d0c839ab51e2877d0578f91a377536de9f97d349e726c718357069bc1\
6479ebca2ae4d11a9f551b6e017939cf249dfa0808584aa823fa088f55fa8ba\
648eb3a6b125087c2ba38fd54fa7e93cfd2b97badf2f968d1608ca42d5e3fb9\
649b7c08c917d698a8f936ad65ad4b4c3b397e81adaf22f73d387086b548a2d\
650be19bd47804f52a729f15e2648218078147f77c6fee0ea3b6411d4f25df1\
651a3bd0afdeb8ae4a8a74d949de9e5df02c92450ebf049d5de98bb6deb59b2\
652acca0845c1076eeee24ac6cf96d61354d84d121af91c7a41972b26a5a440\
653dfb8480c31715404d82ca02974ff8c2bf57146a3f567a48ccd22ca23c932\
6540170bf34c5ddb24ef27912f6c5a7d56edde5fda364f8e0b9a0315d4c056d\
6556eca02168212b5ad61018b7b9b4391a78250f45f9c9499ee01d9429a92fe\
6561222a02c02730703f5cf51dbb9ee976db18cf910b6c9c6f8e5f75a22a358\
657f287e485b614659cca962ee6a89e947b4ce61c24ee3fc7446da2d54ca9aa\
65859c92ee74ecbb7f50ea92a87b925b506737eaf8efbb955fd3c5ed1e17a40\
65958fc66bcefe73e2323b075c5ddc5a57af76aa51aebbca34c777b809ad2c5\
660ad7b8b2d6f927539783ff3cc770d84f00cbc2eb1f8183536ee25c84b7c3f\
661111c151b561403680b1c3079a6ac4e5d41f9a5685962f15bbeaa3466d720\
6621e376a0f8628969e06ed7bf54128b4c46345113fcde67c3bb5d407a40b76\
6639254354f236bd5853b8f4c765b24db9738b2fc98b04fe4d52fb332d157a3\
664813183a275a083386e6856285319477e2ba481261d8792b0258035be52aa\
665a46b28dacffd7a728c3cda63df02b17f567797b359fd10415381af2d95c2\
66677eab3cf0024bdd0853c7e18184586561d3e03a97ecde07b5a2f5026858d\
6675e3459805bddfe235ca5f44376c4ebfe32815e3f9ad6fd4fc116e7dfd459\
6681770282a94b517ba16d37f5439b90f4bba87692fff70aad4dd23cefaaec3\
669650ba160a90ef719965330904677ed6a10a91f9e56fe5ded5b8d25421ea6\
670087584ee379b370b4b1175f1c0fbcf1b36850a7e02e453ace8a6258184b4\
6710cd3c9a81caed79e5b3f965f7d373f800ba5505a804c67d218ba34b79c51\
6723581846c4a04a9015dfdf5d6df009c7eea672e5aea85513fb9745b89d546\
673a4ed8a1a2ad5a44b4696a01c8d9c46293f280162d87067ae65b624f9f93c\
674a48fe7c9a1585a06ec69b3a98da627894bb6f366ba1007f95987aa1aebdb\
675d070b92240a2b5f75260a4395c2d3698eefe1c4929f27e6e48e11f14c701\
676290be0d32ee1f08f4ee3db089170fa7516da8038c1e0530336c3b29d7309\
6773081f270dc62d8b5e3e57fe49f4eee0cbbf81b9e5f5ba77f321d2d65e862\
678a1d534aa71cea5413904e3660235e8edcf4a3618140dfd2f620126978fe0\
679e74367b70475a6ddea3bf8454f152b0d8b695dff664957841e31d87f289d\
6808fff23e81c9654bf1548260ada4c037fb242d8612ae92051820355dc58c7\
681acc0f8f0326542b869f69e5e2f6b81abfbe62703c8b4b9b1a0d425f36c44\
6822d07d17a3d933e9d8475dc06f9de63195f21b76669f67d87ce63a734f86a\
683a28e62034c7b3828fc33921466216289a58eaf0fe727dc3bc77e1b984a83\
68430994092f553912134069211355e5c7ccfc5da62b777008995ce876131ed\
6855627ece154d8821dc256e6d6aa4613c1c9857cb75425199f35172d7d91e8\
686e255741ba00a983bdae35932d6c2f0da0b999bf329870a33396b0f3f0b4e\
687e93dad49db64573bde5864a4b9551b9b9b4234002e2d98bd95b633d70409\
688df6aba73c961b23a9a069db136cb33917401ef57417b222240f0d3cc5067\
689d25d7f67d702fb811dbcd6f39ea9d934412d43c8519f3a869226b5ab302e\
6900bd763536a3efc81b7fcf61a4b53dcfdfb71ffedf0a25d8b993c303f0291\
6917ff6fec362eae004cfd5c218c0b20c46e961cc4bb0930584c30872f2587f\
692ee1de36a22d1bb1e20da1c6959ee4dc3a85f4a9c893c481014edcee883d7\
6934699e1495d26b3babe06c3f783262d37c9ba382a12775cf35b1455560ea6\
694d0b1d52d1e3982fa4bee2a6d0efbb722ef49a02566b2ed3a3813ea4021ae\
69553b565a0762ba4094d7b94b930a93d46f5603dd81e1ea77700d56dc884d5\
69640bf5ddf489034fb779b20e057212c52f648be96acc20f1765354541696b\
6970d688b7fda355b9473484b1f6f08a6b6f77747c6a6b0d5836211de5d75b1\
69870ee1ac62c04b099bd10853563b942642afcf93c65e3237d39b27dab2e8f\
699d98e504b099330ad254c0c0d5b5cd1ab94fcbe87ebe0d57173d8f5be4d8e\
700a0aba0c732747c1c195962ae28617f8519c7cf406e2487a4f6adc3083997\
70158785271d9aa265bb2f67b7e95f49360a2c77ad4147240ff0f51b1ad16a8\
702f6e178bcc044204110444ea2b7e80548769ae5010c22707493adb0baf55f\
7036b979dd5154780b6213d467faefb00000000000000000000000000000000\
704000000060b151a1e24b3def33dabca143013e721533f4f1c5d432de001b3\
7050c452425971fb2f50f5fac02fa24aa22312c4859efbef5066c8affabfc69\
7061c11ef8c1bbec178a532505506";
707 }
708
709 #[test]
711 fn test_ietf_vectors() {
712 let seed_bytes = hex::decode(ietf_vectors::TEST_SECKEY).unwrap();
714 let seed: [u8; 64] = seed_bytes.try_into().unwrap();
715 let seckey = SecretKey::from_bytes(&seed);
716
717 let expected_pubkey = hex::decode(ietf_vectors::TEST_PUBKEY).unwrap();
719 let pubkey = seckey.public_key();
720 assert_eq!(pubkey.to_bytes().to_vec(), expected_pubkey);
721
722 let signature = seckey.sign(ietf_vectors::TEST_MSG.as_bytes());
724 pubkey
725 .verify(ietf_vectors::TEST_MSG.as_bytes(), &signature)
726 .unwrap();
727
728 let expected_sig = hex::decode(ietf_vectors::TEST_SIG).unwrap();
730 let expected_sig_array: [u8; 3373] = expected_sig.try_into().unwrap();
731 let expected_signature = Signature::from_bytes(&expected_sig_array);
732 pubkey
733 .verify(ietf_vectors::TEST_MSG.as_bytes(), &expected_signature)
734 .unwrap();
735 }
736
737 #[test]
738 fn test_pkcs8_encoding() {
739 let seed_bytes = hex::decode(ietf_vectors::TEST_SECKEY).unwrap();
741 let seed: [u8; 64] = seed_bytes.try_into().unwrap();
742 let seckey_from_seed = SecretKey::from_bytes(&seed);
743
744 let pkcs8_bytes = hex::decode(ietf_vectors::TEST_SECKEY_PKCS8).unwrap();
745 let seckey_from_pkcs8 = SecretKey::from_der(&pkcs8_bytes).unwrap();
746
747 assert_eq!(
749 seckey_from_seed.public_key().to_bytes(),
750 seckey_from_pkcs8.public_key().to_bytes()
751 );
752
753 let encoded_der = seckey_from_seed.to_der();
755 assert_eq!(encoded_der, pkcs8_bytes);
756
757 let roundtrip = SecretKey::from_der(&encoded_der).unwrap();
759 assert_eq!(roundtrip.to_der(), encoded_der);
760 }
761
762 #[test]
763 fn test_secretkey_compose_split() {
764 let ml_key = mldsa::SecretKey::generate();
765 let ed_key = eddsa::SecretKey::generate();
766
767 let ml_bytes = ml_key.to_bytes();
768 let ed_bytes = ed_key.to_bytes();
769
770 let composite = SecretKey::compose(ml_key, ed_key);
771 let (ml_key2, ed_key2) = composite.split();
772
773 assert_eq!(ml_key2.to_bytes(), ml_bytes);
774 assert_eq!(ed_key2.to_bytes(), ed_bytes);
775 }
776
777 #[test]
778 fn test_publickey_compose_split() {
779 let ml_key = mldsa::SecretKey::generate().public_key();
780 let ed_key = eddsa::SecretKey::generate().public_key();
781
782 let ml_bytes = ml_key.to_bytes();
783 let ed_bytes = ed_key.to_bytes();
784
785 let composite = PublicKey::compose(ml_key, ed_key);
786 let (ml_key2, ed_key2) = composite.split();
787
788 assert_eq!(ml_key2.to_bytes(), ml_bytes);
789 assert_eq!(ed_key2.to_bytes(), ed_bytes);
790 }
791
792 #[test]
793 fn test_signature_compose_split() {
794 let ml_sec = mldsa::SecretKey::generate();
795 let ed_sec = eddsa::SecretKey::generate();
796
797 let message = b"test message";
798 let ml_sig = ml_sec.sign(message, b"");
799 let ed_sig = ed_sec.sign(message);
800
801 let ml_bytes = ml_sig.to_bytes();
802 let ed_bytes = ed_sig.to_bytes();
803
804 let composite = Signature::compose(ml_sig, ed_sig);
805 let (ml_sig2, ed_sig2) = composite.split();
806
807 assert_eq!(ml_sig2.to_bytes(), ml_bytes);
808 assert_eq!(ed_sig2.to_bytes(), ed_bytes);
809 }
810
811 #[test]
812 fn test_compose_sign_verify() {
813 let ml_sec = mldsa::SecretKey::generate();
814 let ed_sec = eddsa::SecretKey::generate();
815
816 let composite_sec = SecretKey::compose(ml_sec.clone(), ed_sec.clone());
817 let composite_pub = composite_sec.public_key();
818
819 let message = b"message to sign with composite key";
820 let signature = composite_sec.sign(message);
821
822 composite_pub.verify(message, &signature).unwrap();
823 }
824
825 #[test]
826 fn test_separate_sign_compose_verify() {
827 let ml_sec = mldsa::SecretKey::generate();
828 let ed_sec = eddsa::SecretKey::generate();
829
830 let message = b"message to sign separately";
831 let m_prime = split_signing_message(message);
832
833 let ml_sig = ml_sec.sign(&m_prime, SIGNATURE_DOMAIN);
835 let ed_sig = ed_sec.sign(&m_prime);
836
837 let composite_sig = Signature::compose(ml_sig, ed_sig);
839 let composite_pub = PublicKey::compose(ml_sec.public_key(), ed_sec.public_key());
840
841 composite_pub.verify(message, &composite_sig).unwrap();
843 }
844}