darkbio_crypto/mldsa/
mod.rs

1// crypto-rs: cryptography primitives and wrappers
2// Copyright 2025 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! ML-DSA cryptography wrappers and parametrization.
8//!
9//! https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates
10
11use base64::Engine;
12use base64::engine::general_purpose::STANDARD as BASE64;
13use der::asn1::OctetString;
14use der::{Decode, Encode, Sequence};
15use ml_dsa::{EncodedVerifyingKey, MlDsa65};
16use pkcs8::PrivateKeyInfo;
17use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
18use sha2::Digest;
19use spki::der::AnyRef;
20use spki::der::asn1::BitStringRef;
21use spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo};
22use std::error::Error;
23use subtle::ConstantTimeEq;
24use zeroize::Zeroize;
25
26use crate::pem;
27
28/// OID is the ASN.1 object identifier for ML-DSA-65.
29pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.18");
30
31/// Size of the secret key seed in bytes.
32pub const SECRET_KEY_SIZE: usize = 32;
33
34/// Size of the public key in bytes.
35pub const PUBLIC_KEY_SIZE: usize = 1952;
36
37/// Size of a signature in bytes.
38pub const SIGNATURE_SIZE: usize = 3309;
39
40/// Size of a fingerprint in bytes.
41pub const FINGERPRINT_SIZE: usize = 32;
42
43/// ML-DSA-65 private key inner structure.
44#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
45struct MlDsa65PrivateKeyInner {
46    seed: OctetString,
47    expanded: OctetString,
48}
49
50/// SecretKey contains an ML-DSA-65 private key usable for signing.
51#[derive(Clone)]
52pub struct SecretKey {
53    inner: ml_dsa::SigningKey<MlDsa65>,
54    seed: ml_dsa::Seed,
55}
56
57impl Drop for SecretKey {
58    fn drop(&mut self) {
59        self.seed.zeroize();
60    }
61}
62
63impl SecretKey {
64    /// generate creates a new, random private key.
65    pub fn generate() -> SecretKey {
66        let mut seed = ml_dsa::Seed::default();
67        getrandom::fill(&mut seed).unwrap();
68
69        let inner = ml_dsa::SigningKey::<MlDsa65>::from_seed(&seed);
70        Self { inner, seed }
71    }
72
73    /// from_bytes creates a private key from a 32-byte seed.
74    pub fn from_bytes(seed: &[u8; SECRET_KEY_SIZE]) -> Self {
75        let array = ml_dsa::Seed::try_from(seed.as_slice()).unwrap();
76        let inner = ml_dsa::SigningKey::<MlDsa65>::from_seed(&array);
77        Self { inner, seed: array }
78    }
79
80    /// from_der parses a DER buffer into a private key.
81    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
82        // Parse the DER encoded container
83        let info = PrivateKeyInfo::from_der(der)?;
84
85        // Reject trailing data by verifying re-encoded length matches input
86        if info.encoded_len()?.try_into() != Ok(der.len()) {
87            return Err("trailing data in private key".into());
88        }
89        // Ensure the algorithm OID matches ML_DSA_65 (OID: 2.16.840.1.101.3.4.3.18)
90        if info.algorithm.oid != OID {
91            return Err("not an ML-DSA-65 private key".into());
92        }
93        // Wrap the private key in a SEQUENCE containing:
94        //   - OCTET STRING (32 bytes): seed
95        //   - OCTET STRING (4032 bytes): expanded key
96        let inner_key = MlDsa65PrivateKeyInner::from_der(info.private_key)?;
97
98        let seed: ml_dsa::Seed = inner_key
99            .seed
100            .as_bytes()
101            .try_into()
102            .map_err(|_| "seed not 32 bytes")?;
103        let expanded: [u8; 4032] = inner_key
104            .expanded
105            .as_bytes()
106            .try_into()
107            .map_err(|_| "expanded key not 4032 bytes")?;
108
109        // Generate key from seed and validate it matches the expanded key in DER
110        let inner = ml_dsa::SigningKey::<MlDsa65>::from_seed(&seed);
111
112        #[allow(deprecated)] // to_expanded is wasteful, but that's the DER spec
113        let enc = inner.to_expanded();
114        if enc.as_slice().ct_ne(&expanded).into() {
115            return Err("expanded key does not match seed".into());
116        }
117        Ok(Self { inner, seed })
118    }
119
120    /// from_pem parses a PEM string into a private key.
121    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
122        // Crack open the PEM to get to the private key info
123        let (kind, data) = pem::decode(pem_str.as_bytes())?;
124        if kind != "PRIVATE KEY" {
125            return Err(format!("invalid PEM tag {}", kind).into());
126        }
127        // Parse the DER content
128        Self::from_der(&data)
129    }
130
131    /// to_bytes returns the 32-byte seed of the private key.
132    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE] {
133        let mut out = [0u8; 32];
134        out.copy_from_slice(self.seed.as_slice());
135        out
136    }
137
138    /// to_der serializes a private key into a DER buffer.
139    pub fn to_der(&self) -> Vec<u8> {
140        #[allow(deprecated)] // to_expanded is wasteful, but that's the DER spec
141        let enc = self.inner.to_expanded();
142
143        let inner_key = MlDsa65PrivateKeyInner {
144            seed: OctetString::new(self.seed.as_slice()).unwrap(),
145            expanded: OctetString::new(enc.as_slice()).unwrap(),
146        };
147        let inner = inner_key.to_der().unwrap();
148
149        let alg = pkcs8::AlgorithmIdentifierRef {
150            oid: OID,
151            parameters: None::<AnyRef>,
152        };
153        let info = PrivateKeyInfo {
154            algorithm: alg,
155            private_key: &inner,
156            public_key: None,
157        };
158        info.to_der().unwrap()
159    }
160
161    /// to_pem serializes a private key into a PEM string.
162    pub fn to_pem(&self) -> String {
163        pem::encode("PRIVATE KEY", &self.to_der())
164    }
165
166    /// public_key retrieves the public counterpart of the secret key.
167    pub fn public_key(&self) -> PublicKey {
168        PublicKey {
169            inner: self.inner.verifying_key(),
170        }
171    }
172
173    /// fingerprint returns a 256bit unique identified for this key. For ML-DSA,
174    /// that is the SHA256 hash of the raw public key.
175    pub fn fingerprint(&self) -> Fingerprint {
176        self.public_key().fingerprint()
177    }
178
179    /// sign creates a digital signature of the message with an optional context string.
180    pub fn sign(&self, message: &[u8], ctx: &[u8]) -> Signature {
181        let sig = self.inner.sign_deterministic(message, ctx).unwrap();
182        let encoded = sig.encode();
183        let slice: &[u8] = encoded.as_ref();
184        Signature(slice.try_into().unwrap())
185    }
186}
187
188/// PublicKey contains an ML-DSA-65 public key usable for verification.
189#[derive(Debug, Clone)]
190pub struct PublicKey {
191    inner: ml_dsa::VerifyingKey<MlDsa65>,
192}
193
194impl PublicKey {
195    /// from_bytes converts a 1952-byte array into a public key.
196    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE]) -> Self {
197        let enc = EncodedVerifyingKey::<MlDsa65>::try_from(bytes.as_slice()).unwrap();
198        let inner = ml_dsa::VerifyingKey::<MlDsa65>::decode(&enc);
199        Self { inner }
200    }
201
202    /// from_der parses a DER buffer into a public key.
203    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
204        let info: SubjectPublicKeyInfo<AlgorithmIdentifier<AnyRef>, BitStringRef> =
205            SubjectPublicKeyInfo::from_der(der)?;
206
207        // Reject trailing data by verifying re-encoded length matches input
208        if info.encoded_len()?.try_into() != Ok(der.len()) {
209            return Err("trailing data in public key".into());
210        }
211        if info.algorithm.oid != OID {
212            return Err("not an ML-DSA-65 public key".into());
213        }
214        let key = info.subject_public_key.as_bytes().unwrap();
215        if key.len() != 1952 {
216            return Err("public key not 1952 bytes".into());
217        }
218        let bytes: [u8; 1952] = key.try_into()?;
219        let enc = EncodedVerifyingKey::<MlDsa65>::try_from(bytes.as_slice()).unwrap();
220        let inner = ml_dsa::VerifyingKey::<MlDsa65>::decode(&enc);
221        Ok(Self { inner })
222    }
223
224    /// from_pem parses a PEM string into a public key.
225    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
226        let (kind, data) = pem::decode(pem_str.as_bytes())?;
227        if kind != "PUBLIC KEY" {
228            return Err(format!("invalid PEM tag {}", kind).into());
229        }
230        Self::from_der(&data)
231    }
232
233    /// to_bytes converts a public key into a 1952-byte array.
234    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
235        let enc = self.inner.encode();
236        let mut out = [0u8; 1952];
237        out.copy_from_slice(enc.as_slice());
238        out
239    }
240
241    /// to_der serializes a public key into a DER buffer.
242    pub fn to_der(&self) -> Vec<u8> {
243        let enc = self.inner.encode();
244        let bytes = enc.as_slice();
245
246        let alg = AlgorithmIdentifier::<AnyRef> {
247            oid: OID,
248            parameters: None::<AnyRef>,
249        };
250        let info = SubjectPublicKeyInfo::<AnyRef, BitStringRef> {
251            algorithm: alg,
252            subject_public_key: BitStringRef::from_bytes(bytes).unwrap(),
253        };
254        info.to_der().unwrap()
255    }
256
257    /// to_pem serializes a public key into a PEM string.
258    pub fn to_pem(&self) -> String {
259        pem::encode("PUBLIC KEY", &self.to_der())
260    }
261
262    /// fingerprint returns a 256bit unique identified for this key. For ML-DSA,
263    /// that is the SHA256 hash of the raw public key.
264    pub fn fingerprint(&self) -> Fingerprint {
265        let mut hasher = sha2::Sha256::new();
266        hasher.update(self.inner.encode().as_slice());
267        Fingerprint(hasher.finalize().into())
268    }
269
270    /// verify verifies a digital signature with an optional context string.
271    pub fn verify(
272        &self,
273        message: &[u8],
274        ctx: &[u8],
275        signature: &Signature,
276    ) -> Result<(), ml_dsa::Error> {
277        let sig = ml_dsa::Signature::<MlDsa65>::try_from(signature.to_bytes().as_slice())?;
278        if self.inner.verify_with_context(message, ctx, &sig) {
279            Ok(())
280        } else {
281            Err(ml_dsa::Error::default())
282        }
283    }
284}
285
286impl Serialize for PublicKey {
287    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
288        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
289    }
290}
291
292impl<'de> Deserialize<'de> for PublicKey {
293    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
294        let s = String::deserialize(deserializer)?;
295        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
296        let arr: [u8; PUBLIC_KEY_SIZE] = bytes
297            .try_into()
298            .map_err(|_| de::Error::custom("invalid public key length"))?;
299        Ok(PublicKey::from_bytes(&arr))
300    }
301}
302
303#[cfg(feature = "cbor")]
304impl crate::cbor::Encode for PublicKey {
305    fn encode_cbor(&self) -> Vec<u8> {
306        self.to_bytes().encode_cbor()
307    }
308}
309
310#[cfg(feature = "cbor")]
311impl crate::cbor::Decode for PublicKey {
312    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
313        let bytes = <[u8; PUBLIC_KEY_SIZE]>::decode_cbor(data)?;
314        Ok(Self::from_bytes(&bytes))
315    }
316
317    fn decode_cbor_notrail(
318        decoder: &mut crate::cbor::Decoder<'_>,
319    ) -> Result<Self, crate::cbor::Error> {
320        let bytes = decoder.decode_bytes_fixed::<PUBLIC_KEY_SIZE>()?;
321        Ok(Self::from_bytes(&bytes))
322    }
323}
324
325/// Signature contains an ML-DSA-65 signature.
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub struct Signature([u8; SIGNATURE_SIZE]);
328
329impl Signature {
330    /// from_bytes converts a 3309-byte array into a signature.
331    pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Self {
332        Self(*bytes)
333    }
334
335    /// to_bytes converts a signature into a 3309-byte array.
336    pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
337        self.0
338    }
339}
340
341impl Serialize for Signature {
342    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
343        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
344    }
345}
346
347impl<'de> Deserialize<'de> for Signature {
348    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
349        let s = String::deserialize(deserializer)?;
350        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
351        let arr: [u8; SIGNATURE_SIZE] = bytes
352            .try_into()
353            .map_err(|_| de::Error::custom("invalid signature length"))?;
354        Ok(Signature::from_bytes(&arr))
355    }
356}
357
358#[cfg(feature = "cbor")]
359impl crate::cbor::Encode for Signature {
360    fn encode_cbor(&self) -> Vec<u8> {
361        self.to_bytes().encode_cbor()
362    }
363}
364
365#[cfg(feature = "cbor")]
366impl crate::cbor::Decode for Signature {
367    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
368        let bytes = <[u8; SIGNATURE_SIZE]>::decode_cbor(data)?;
369        Ok(Self::from_bytes(&bytes))
370    }
371
372    fn decode_cbor_notrail(
373        decoder: &mut crate::cbor::Decoder<'_>,
374    ) -> Result<Self, crate::cbor::Error> {
375        let bytes = decoder.decode_bytes_fixed::<SIGNATURE_SIZE>()?;
376        Ok(Self::from_bytes(&bytes))
377    }
378}
379
380/// Fingerprint contains a 32-byte unique identifier for an ML-DSA-65 key.
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub struct Fingerprint([u8; FINGERPRINT_SIZE]);
383
384impl Fingerprint {
385    /// from_bytes converts a 32-byte array into a fingerprint.
386    pub fn from_bytes(bytes: &[u8; FINGERPRINT_SIZE]) -> Self {
387        Self(*bytes)
388    }
389
390    /// to_bytes converts a fingerprint into a 32-byte array.
391    pub fn to_bytes(&self) -> [u8; FINGERPRINT_SIZE] {
392        self.0
393    }
394}
395
396impl Serialize for Fingerprint {
397    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
398        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
399    }
400}
401
402impl<'de> Deserialize<'de> for Fingerprint {
403    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
404        let s = String::deserialize(deserializer)?;
405        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
406        let arr: [u8; FINGERPRINT_SIZE] = bytes
407            .try_into()
408            .map_err(|_| de::Error::custom("invalid fingerprint length"))?;
409        Ok(Fingerprint::from_bytes(&arr))
410    }
411}
412
413#[cfg(feature = "cbor")]
414impl crate::cbor::Encode for Fingerprint {
415    fn encode_cbor(&self) -> Vec<u8> {
416        self.to_bytes().encode_cbor()
417    }
418}
419
420#[cfg(feature = "cbor")]
421impl crate::cbor::Decode for Fingerprint {
422    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
423        let bytes = <[u8; FINGERPRINT_SIZE]>::decode_cbor(data)?;
424        Ok(Self::from_bytes(&bytes))
425    }
426
427    fn decode_cbor_notrail(
428        decoder: &mut crate::cbor::Decoder<'_>,
429    ) -> Result<Self, crate::cbor::Error> {
430        let bytes = decoder.decode_bytes_fixed::<FINGERPRINT_SIZE>()?;
431        Ok(Self::from_bytes(&bytes))
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    // Tests that a PEM encoded ML-DSA-65 private key can be decoded and re-encoded
440    // to the same string. The purpose is not to battle-test the PEM implementation,
441    // rather to ensure that the code implements the PEM format other subsystems
442    // expect.
443    #[test]
444    fn test_secretkey_pem_codec() {
445        // Generated with:
446        //   openssl genpkey -algorithm mldsa65 -out test.key
447        let input = "\
448-----BEGIN PRIVATE KEY-----
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
534sufdPOOp/GHJAqEQz7n/Owe/
535-----END PRIVATE KEY-----";
536
537        let key = SecretKey::from_pem(input).unwrap();
538        assert_eq!(key.to_pem().trim(), input.trim());
539    }
540
541    // Tests that a PEM encoded ML-DSA-65 public key can be decoded and re-encoded
542    // to the same string. The purpose is not to battle-test the PEM implementation,
543    // rather to ensure that the code implements the PEM format other subsystems
544    // expect.
545    #[test]
546    fn test_publickey_pem_codec() {
547        // Generated with:
548        //   openssl pkey -in test.key -pubout -out test.pub
549        let input = "\
550-----BEGIN PUBLIC KEY-----
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
5929w5RU0Pq
593-----END PUBLIC KEY-----";
594
595        let key = PublicKey::from_pem(input).unwrap();
596        assert_eq!(key.to_pem().trim(), input.trim());
597    }
598
599    // Tests that a DER encoded ML-DSA-65 private key can be decoded and re-encoded
600    // to the same string. The purpose is not to battle-test the DER implementation,
601    // rather to ensure that the code implements the DER format other subsystems
602    // expect.
603    #[test]
604    fn test_secretkey_der_codec() {
605        // Generated with:
606        //   openssl pkey -in test.key -outform DER -out test.der
607        //   cat test.der | xxd -p
608        let input = "\
60930820ffe020100300b060960864801650304031204820fea30820fe60420
6101c8c861052c0a87dc2354e9786e2979b74bd90ccf4ca593b621cdcdd5ac3
6113cf904820fc058cc31abeb220f121b1f81fe955cb544416bf8ab1e5f9083
6123b2f94cb4a7bec4dabc97e7bdab615f3f3880d25c6f274a6c935bcb9c107
613b533633c170514365b012dc98b1ced341e43c01d070166637f2d01021cac
614ffbcc8dd869c0b6ea3ee6cdf1495e58d8f4cae8bf45104b25652d7bab381
6156485c23de053c4b4d790a25f429870407712601340300584081034332068
616614418125728223031622702145512165128731856864625548002388518
617073482068401241773173780541554226728313070806004387030568443
618571131467863623258884082048655217450612136110411708344680082
619767808238641361653573388167381487256172370746444247503230647
620468501334153744156888770746470162663502254182218368500228556
621103434715416507316156715700865600278563700401416767833424226
622233430280441811612022353483637701653711888161026481353324677
623082116648064142870267705868634642763736506840818703627673141
624706614140473010370417224061680450776772382834260381106867616
625364412261424586606437808678633583217808668422835731856363028
626454177430651220758281660716062380064622584472668828701621216
627851152365331616307422641446373732506162841041456605233214156
628548412761718782827647344200147135777887483534471863347230128
629113174438245853245157011115288857400761010601280604608142786
630245437365538332605307164643173702555370045000734531082246257
631723072126400468333714651042488385506110534030612366477681587
632577002412226442148885565373110851045400083576378062170252805
633781785378704517185103804445261601873467814062861031534111208
634733238258886062062538136475605485526883246402480471078264315
635485234672576223575148432458624537103547267173433750122665830
636152306435033640075723360628640336054523225483282530287267424
637322812665777513031560250831252585337430034818337552858765750
638280876811865322103771531783612364722560865065583510838663768
639572342461348330451824512255034374388482238424140845141658056
640071821370835151517515578172026037122757372455651445036823642
641363433505344447387740447178530605846728205174864857010682638
642484317162264886282585237284163638444130465588544034166868117
643638225211508081700542353653757770313376110712100063736412527
644616484813617636302268667853302244081277785767856026267576486
645778448473427032744824504816632367188410618257142655140807470
646455226243652665665531308122538236716166864551606232115280366
647182566817033431310807766188008387861806284718081010448103756
648168662775682613362536416232338732247641724856286268022280772
649701771375608621272133437540167440307018700804452263305118614
650707751257533006560522260525216028180673254772254864348736085
651046786625522274825664317608486342843854776518258274034736822
652858386428004378721706155845481321402014823223102407872032865
653002363747024054785537126127835471101022411750428142466788885
654170874142304025840118035606644665770755805681420402536072454
655388305071877608680170374338346117277433111817481320244553118
656232313587440137270122751271614887186755020578740843403332422
657427351881018741003557634071787600558816708240064460461732316
658171450276357514254787467215272342878726132070425748646677658
659771504257758808348331806870357175258612846610524564025407326
660153848734147053437242745772240824841080767627126250025262122
661115043011340234886136401837153214213712240824331368758384185
6628071707642855313325353583fb51a1d66528878c0902ccd88bbdd3278b5
6634adf60e1ecdded58e0401f10d080b908006a5c3588614e3b0555b11e0d7b
664a11ad5a877420e5b197b2e6faf02ac19d27603f44b3b371d80d28034026b
665b55f19b913d659e431788f3a834d74cc094d1c104687e8a69ed6a239c778
666453544a8a7def10be3dcfa8bbf6bda9164aa2e0d4482fd882bdca467f2fb
667d10795be2b90ddd202ad5821373038a7ab744a923e28f1ff146e42d70679
6687ee41dae3bf972e85a3c8d6c8230638bf5bbb9211ca5735f2698cdc7db0c
6694291d72f1c816614bbf4cf28811844fc2837f4ba4f941ede90fd22bdb022
670112b285a30183b31fd4d77cb4f72b75827652394ea33fe93ae83c6995c64
671554e21d099eddc3c143c6da53153174a24da20928127a2482989026b3329
672af4791056b41f149456cd5a20d01b95b9b2d95baae52f22cc78cc82400ab
67377a8c5804891e8d3c2eabd1b79364c629721cdc561157e373b73e655f8f8
674018421538ed11b3426a203bd146794c33e2d03f5de55f0104fe182b73721
675e650f66b3f696198798c922ef8313e196c34d4f8fc9c50a89fdf9ba99b51
676ab3691dbfa9adf5f5d219e48ad2d06a387e3b44edfffabfd535374d576af
677bcbee84bf31f8ae4e788e701ec5d3d457eaa957dfa2f2a056ab57c446e5d
6783e9d1a3a1d6f01796756395bc5e3940ac830c5af36abfeea58f37bf02421
679f774576498475953089b471030e679acbc1cb113789f1cf1f37b80e333b2
680890e1537c37be96d61b55ab05dfc33f4ea7f21a3769c963a30a629104ae8
681c8e02bfc1e4b6ef358559bc502ebc04e77ba3f0f8cf79b21feacedfd3f91
6820e9dc4528322f1b0e595cc000a2493593fb3d8aeffbe51aba39cd7f89ccf
6830225cfa3cbbc9595cb71acf2d85b4f6493d563aad224c9dcd0f3ddab5d71
68436acb7cf8797cbe36721961db28405d5c54a5a49b597bef687972b26beb2
6856830a1f1e1d2a291e1b715326316118e125d7991afcdebc5fa7e7bcd1211
6863a702f677b16df4ba750644fb90c6abd2779d90a0be0c14cc94ad74972e5
687ed804e84936dce1eeb2168655bcd463255ddd3703b65d442cc6101dc1855
6888eab72e38498d297262c077de6165dfd7b18a3981e885125d558e8776828
68919ce113d30119cded255a4f23a860751db1f0ce4c2cf9a159dd1b3b0b3b9
6901526239912f4f76c3d8cf68370c92a3f6e73b24f3d5161d10ede7847eede
691d15cbf6c77ade44995048da619d91842d5039f8517781424c0a0b38d9232
69241ad9df6b3fcfea6ed4a4e7b8eef62f34846912b2b413aa290fc66a89f16
693868eb140283149926a2e307ef21449151b630a5ddcd1d979cc1d0fbf7bf2
694a8c444b2d4a40f810702d4dc2cfb2c837a6475c05c87d1b17fa0d4be03ab
6959232bc790ac177609936ad741185d53d9248c7bc7b82fe764dab0bc4051d
696b890f9996a26b55a33f1359fbd2a08c273609a652a0dd75c7eac3b0b677a
697ccff51f5a8898e41c374f478010f86e4d5847eb03edbae397abdb6deb588
698910c67d6d2a1df0acfc6df7fae4b8b09b1f844f522fbc0a5698bf7d61d90
69963430cb64c6af994e45ccb2cd3e22f19a80da1014189ff565af15c3f4f0f
700b9e38060dbce788853491458f94b53dee37e3712ecc26779dcf079fa79dc
7015aeed5b95c01cf4a864cc2e5739a524b65da677848709dfc841bec4d296d
702cd59801c984d30e18958068798271c50763ee5c99acdb6ad6791f0792a3f
703d3feee5f120bb510b7f4c7ac47e5cee537655c7bf292ad2d0e80ffe3af92
7042c1ac0a8ce60387e6a0fd8ce120b67c0dbd0f02bcd0a4cd05b9ecf9c0f45
7057cc63dff146df07f8ebc85508308b04aab436e0b5f19d728d5529e2f10a7
70680d169b1ccef7bf97c76dac909a788de50c30b7fb5c2cd726530dce3d43b
707ad932c90f6c8c611efb03c677fbd6213989899ad553a7994a4a9dd2aa809
708f11852a9b0e6bba479d3ba26d2a06773d7fb431060e5d7da62f124cbd7d1
70950eb67ce833d70ef5c6f2779f20c6a3f449dc8b47bbb606cc118d7d3eac7
71026d6ab73990543f70f2212c3c5574e6cbdb956507940138525e95917fccf
7110358b1cc47aab70988bdd8964bb0c98f3fb5718190c2c0a7f17f6342d333
712f0c79cae76989aff5b1acb16e6b07cb28bb306e03b375feb2d02005f8ebc
7133ec70a8ba65a501e73fcb59f507a3fbc02f60f4e3d893895cd223cfc90ea
714c08179a1442e1f808f8dc3edd3fa16379bd4d2dd2da134a52e145fa81490
71562131c8d9273bc8e044ff4197265761aaa83468540b910ace8476ceb4f00
716a37c023cae7c321f7f771fab180896f1bf1d7de119ed66bc07d595f2c5cf
71764408ead30e0b114c4db472e877f88d7c6d5148e0626b5e5737d1ec75371
718c1a11ff757a16995fed5eb1871a60fa844da47acee368c90275f232e5091
719bddd83fcf493603dd7296f97ca19583b43770cfef0cd570df2630c354376
720e9b3af6831d51e9b0317092164a4512a26f290eb313a7298b52d2b8cf575
72117950b772de6690ff946785b068882fa186b5c1654f1266e93401c3c4b6b
722f365b2dca4387395672675e4eaee5a2c3e758c6ec5699d601fe817b4d1aa
723981a98c975f29fddd7b883c04a0ca6ac24cea36835d6f41d3dd1f3df06d3
72430d1cc84921530b73dee77b120321e26a33eb2972c5e753c8a1e50fab7cd
7256c6e7ca3457bc4673901de867b8cba139cfd9d0c31a1a56268db6de9e955
726724ba1a6aca866ed8abb2566a65ec8f34fe3ab1e29fb9f98a8dc81460fd3
72788a6ecdd2b05165cfc2ff29198ad92024e1eb34a501a93586c56378993bc
7289b13767f1099e0d8b731ae70f96dd1237b98ce0304df1f7c8dcb11d48319
729e52112f415889c5ea859121a797cb19480fb3179ac3087f3e892b51eae03
730a8fd356d925253ed68b9f2a077a0e84b8adb1477cef35ae1616e36821fc6
7310dd67c54de4d93e6883e25573ba191754281b20c3334e9d89b3135f91355
732235189d58206dceefa4e106af0c9aa4b83b8e5723de7cf3530e44c079fa5
733e4edef4d23fea71a5fc0c6fb64bbccc206bcd615ee23c14f232940fd131d
7347b6d0ec824065734d2ed73134a2bde6be8de4d3adc751be9c110f35dd05a
735ee1923a5619d2985dda8e81d73aa0a2dc2949570a2adabf3781662e517aa
7367772aba7acfb33787d9741da5f6acbf51302e62ec50af170a81d62a09fea
737127324907108b54c95817787326a02271a5911a2a76f3c027c4a758ab272
738e14ca6199fb118e9e7d847cc6e0d3aef32e32437812221bf5c2a6982e2f1
73948d44382adc12d9c09a427448c60affae93d838f33a36325f4252fcfa5ba
7406a5e4184fdfdfea8f1b5b3edbe71204aafc532e30fafb33dd88677158872
74132b3fa6cd81e238f4822ba24379d95dc0da2f18ea5aac31840d6a7baf5a6
742163c54c750908e249e2f794757fd2357c3916caeba4ec2fbbb4f0d2f42fa
743b4d4ddf03d3f6ef18fd7543c395409fa43a375b8c86247c7303270cc4355
744ff8ef5f8aa1245dabdd21b7145f37e06bb6e8eccf132f5ca1b4e30a6ea15
745b2e7dd3ce3a9fc61c902a110cfb9ff3b07bf"
746            .trim()
747            .replace("\n", "");
748
749        let der = hex::decode(&input).unwrap();
750        let key = SecretKey::from_der(&der).unwrap();
751        assert_eq!(hex::encode(key.to_der()), input);
752    }
753
754    // Tests that a DER encoded ML-DSA-65 public key can be decoded and re-encoded
755    // to the same string. The purpose is not to battle-test the DER implementation,
756    // rather to ensure that the code implements the DER format other subsystems
757    // expect.
758    #[test]
759    fn test_publickey_der_codec() {
760        // Generated with:
761        //   openssl pkey -in test.key -outform DER -pubout -out test.pub
762        //   cat test.pub | xxd -p
763        let input = "\
764308207b2300b0609608648016503040312038207a10058cc31abeb220f12
7651b1f81fe955cb544416bf8ab1e5f90833b2f94cb4a7bec4d233a14ca86b6
7665061675fb6d1d63453a0b83a3b38db3d96737d7c36573538209609b8b430
767613624bf08de2d70b5ff1542fc4ae0ee3f99b18febecd97a47277c35ce35
76872c96250c7f8b3984a1b65ce5f5fc3a541b418a6e37a59b3454b3c8ebc94
769fb450160d404c10a2afd721f8ee29c90194919bf852d445adee0635ff4e1
7700ae3d9d2419bcf99903112b3c3f897c0f918b8a3879d1cd88a89daee61d2
77177f311e57abde344530236d0a784aba1c8c0f12d9a756256a392fc782463
7728549f2b6f4ca979b6f055a97332df266cd57c4f1f0eec7ec57b30049b1e5
7731bd925679debe71271041a1bff25044bc2ccdd01c0c4930683cd7f4887ad
774e6aea3841f80da75f01d090aa073a67be8c825138d6e2b1cf0cdb19fbc83
77591dbc45fff69f4ff4c2e60f110a24f5c1e118e8f9369b3d2562194e6747c
77666154ba398134bdda177aca7637236e754b3089ddaf9edbc59abcb52be5e
777f34aa5dc2ef86e9f6c168a1a34f1d6efc4f02c4061af5baba25eb3d801ba
778cd2d40d05e377dc2e9dde162e4f38043c0ee4be9307da863bc7a069707eb
7799363675d84548b77b9a00b6e18be146b0e1ea46d3e130f5bf6d8454b7541
7800f4c334a475e66745877a80f5d8f99d2d81a72b00edea72519dfeff774d4
7819793b40af1767ac0c8b25ab021fa9a9256d4ef8c0dc8c115a651f669475f
7820b98c1e71cda8b253cd765f1280b3506d7b30e7f9bf85afcd7ec8f06e649
7832a48ff59f92e357801d5a3ca359efede736caffb5b79a09db82c9f8c6636
784f67163833e288530692ce0a74a1754206275cde79a1dbcc798e2bc873831
7857d4411e7a4290be027a72210abb906ee18f0ed575a7e321196406f959c39
786685d6c4c8de7bcf0589823690ca87b0baeea09e57cb0e4bdebbfe1e7cdc5
787105977b608d1dd0ed4c27d7c4c51da09c0239adc5cda1a93157e8039f6ab
788c55b9d9d083c54a3da2f58b806e85e715d54c126481be8cf9dd39590f707
78943ab12da8a186a0e1a7198c66e48e56c984a99dc055e66b85b324cc02707
7902e382b0dae7d540c8ae721f099ce70d0710d2b7f1e7f7e97b4d0645ea4d1
7919531166c71ed5f3e373808213581f19c645e765807419f82e7ce7a9c1f67
7925000ea96df79d20c0c401f861ae30c6770330e480a56149fcdec8a764d4c
7937f246e48c1a482c28aebcc6ff305e3e1fdf644a8a675737b118a93b1e77f
794bfc800ad67d342dc2a141fccb9793878e73b93301b4658231e4593c488ee
7957482503e7438a913fd9a297d7604ae1e990cf8a2c79347ae455d704fc526
796a5efc9a2c50dec9037866e9b63f46ac84aa985cdc28e99f223a3c034ac05
797ce8b4d229a24dd5649a1d69e7abf0d89240e8428c248811b38f42d5d447d
7981add4348009c3b687d27d8c1f13652d378385bb3c026e14cf93b4e9b2d1f
7998f7f0aee76dd151b447c70fa58290cb24ea7b97c6036b6f0d2a1b30376de
80044d304d7c81680d4c5a671e60e19892408e755d90bbaf958e2e79d3b2c88
801f4dc2381183f3f30eb02a5a756061a98d2313542aba8c775b111ddf5f50d
802830e5c5077abaede047abb114abf8442422b6cbe852b5dd289d5f0a331af
8032caa725c4a0f8565eb2eb330cb7b4e972ebc3c585e24a28ffcde7fdea7fb
804854d5bace20cd3e5403f162347b1b24217a5716217067fe367b15c2e6ba7
8053e773df977a0ce2b13097c31fcd16526ef41341d5ec38aa8abaef8051df7
8066fb00d4a8b5748036b2e1622b9736a6bcada3a01e18f9dbd989c48d806f1
8073d622be3c373818cf33b7916b54dd7876ee6bea8080e35e76d64c4aa672d
808b9c037d42f7ba8bc168923bf9a1dd187093a4acfda33eab5f70d303158e9
80920bff261a3f34c6a261683013256ca8b03f0c7670810a10b9bdb60d8e8e6
8104f2d63e9f9d456f0679efc1237b08a5ee915de30f92106562c083fbc5df3
8117116e3a9b03a9716691270548e040e22988e622a80a1310994779f6578c1
8123102688150d0c3a7bf7850b98995a02bc013f799e8d35d08941167e533a6
81360a9472e4acdb606051b83921ab086fb3d613c1f57094c593dfb8fb713ac
814740d321807d6c1c5e62bf7d3afcf930c941b50870eb26e18eaf4c63f3184
81501ce196e10994c4502ed8b7dcc58c4e086c7bd18324ddc193f7551ffeb30
8169fa28ad2168269ad9692e53c9940540162891e9f854ba4145d5102f40d81
817f81c8744ee8fd97983163784c1c43016daa0de55a2e16c5949fe3737ee84
818d04f881f20fd50d1f8bbc58dd5db3eb4357393e4859183f62db69cff2eb3
819706ad5c9e3dea63441c39d175d97f659d3dc0c2e0d206991e4fad0b861e8
820cc8f898658bb0ab8eac53e3add5d1b0eeff2497b259db6cde0f6dcda6a7a
821272f19ffe749e14ab8b0bf208a5b45fd67b3d0e3723f6f94b1196b0771d8
82294a4a1607ea66d79e0409867a66d4c255523c8bd3ae1efe7189d6c1bf1ce
82362eee5cc73e247e9a07b25a4a3cbd51aad22c31d87f11bd452a8d7131fe3
824a080d67bec8528778cf48d001dd81b5358adb6c40c11527685e5dc135fdd
82524fab0b25e746ce1a7c826234a14606ed6d021f160974dfe68e98eb98019
826dbdfd1dd95f38d72218ceeae2461974019c705ef7d2d16a56e7b50a0cd51
8278aa9f4772c47a5209f7033bd3191b017eb083f94615abcb3c777d661383c
828891ce8c5a6e2e35cd9b025ae41b1909dbe79bb3bb2da7675cd3b96b629e5
82998005f4479c0a9be004a433553f69a081c15f70e515343ea"
830            .trim()
831            .replace("\n", "");
832
833        let der = hex::decode(&input).unwrap();
834        let key = PublicKey::from_der(&der).unwrap();
835        assert_eq!(hex::encode(key.to_der()), input);
836    }
837
838    // Tests signing and verifying messages. Note, this test is not meant to test
839    // cryptography, it is mostly an API sanity check to verify that everything
840    // seems to work.
841    //
842    // TODO(karalabe): Get some live test vectors for a bit more sanity
843    #[test]
844    fn test_sign_verify() {
845        // Create the keys for Alice
846        let secret = SecretKey::generate();
847        let public = secret.public_key();
848
849        // Run a bunch of different authentication/encryption combinations
850        struct TestCase<'a> {
851            message: &'a [u8],
852            ctx: &'a [u8],
853        }
854        let tests = [
855            TestCase {
856                message: b"message to authenticate",
857                ctx: b"",
858            },
859            TestCase {
860                message: b"message to authenticate",
861                ctx: b"application context",
862            },
863        ];
864
865        for tt in &tests {
866            // Sign and verify the message
867            let signature = secret.sign(tt.message, tt.ctx);
868            public.verify(tt.message, tt.ctx, &signature).unwrap();
869
870            // Verify wrong context fails
871            assert!(
872                public
873                    .verify(tt.message, b"wrong context", &signature)
874                    .is_err()
875            );
876        }
877    }
878
879    #[test]
880    fn test_bad_pubkey_does_not_panic() {
881        // Ensure malformed public keys don't crash
882        let zeros = [0u8; 1952];
883        let _ = PublicKey::from_bytes(&zeros);
884
885        let ones = [0xFFu8; 1952];
886        let _ = PublicKey::from_bytes(&ones);
887    }
888}