darkbio_crypto/xdsa/
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//! Composite ML-DSA cryptography wrappers and parametrization.
8//!
9//! https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs
10
11mod 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
24/// Prefix is the byte encoding of "CompositeAlgorithmSignatures2025" per the
25/// IETF composite signature spec.
26const SIGNATURE_PREFIX: &[u8] = b"CompositeAlgorithmSignatures2025";
27
28/// Label is the signature label for ML-DSA-65-Ed25519-SHA512.
29pub const SIGNATURE_DOMAIN: &[u8] = b"COMPSIG-MLDSA65-Ed25519-SHA512";
30
31/// OID is the ASN.1 object identifier for MLDSA65-Ed25519-SHA512.
32pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.6.48");
33
34/// Size of the secret key in bytes.
35/// Format: ML-DSA seed (32 bytes) || Ed25519 seed (32 bytes)
36pub const SECRET_KEY_SIZE: usize = 64;
37
38/// Size of the public key in bytes.
39/// Format: ML-DSA (1952 bytes) || Ed25519 (32 bytes)
40pub const PUBLIC_KEY_SIZE: usize = 1984;
41
42/// Size of a composite signature in bytes.
43/// Format: ML-DSA (3309 bytes) || Ed25519 (64 bytes)
44pub const SIGNATURE_SIZE: usize = 3373;
45
46/// Size of a key fingerprint in bytes.
47pub const FINGERPRINT_SIZE: usize = 32;
48
49/// SecretKey is an ML-DSA-65 private key paired with an Ed25519 private key for
50/// creating and verifying quantum resistant digital signatures.    
51#[derive(Clone)]
52pub struct SecretKey {
53    ml_key: mldsa::SecretKey,
54    ed_key: eddsa::SecretKey,
55}
56
57impl SecretKey {
58    /// generate creates a new, random private key.
59    pub fn generate() -> SecretKey {
60        SecretKey {
61            ml_key: mldsa::SecretKey::generate(),
62            ed_key: eddsa::SecretKey::generate(),
63        }
64    }
65
66    /// compose creates a secret key from its constituent ML-DSA-65 and Ed25519
67    /// secret keys.
68    pub fn compose(ml_key: mldsa::SecretKey, ed_key: eddsa::SecretKey) -> Self {
69        Self { ml_key, ed_key }
70    }
71
72    /// split decomposes a secret key into its constituent ML-DSA-65 and Ed25519
73    /// secret keys.
74    pub fn split(self) -> (mldsa::SecretKey, eddsa::SecretKey) {
75        (self.ml_key, self.ed_key)
76    }
77
78    /// from_bytes creates a private key from a 64-byte seed.
79    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    /// from_der parses a DER buffer into a private key.
90    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
91        // Parse the DER encoded container
92        let info = pkcs8::PrivateKeyInfo::from_der(der)?;
93
94        // Reject trailing data by verifying re-encoded length matches input
95        if info.encoded_len()?.try_into() != Ok(der.len()) {
96            return Err("trailing data in private key".into());
97        }
98        // Ensure the algorithm OID matches MLDSA65-Ed25519-SHA512
99        if info.algorithm.oid != OID {
100            return Err("not a composite ML-DSA-65-Ed25519-SHA512 private key".into());
101        }
102        // Private key is ML-DSA seed (32) || Ed25519 seed (32) = 64 bytes
103        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    /// from_pem parses a PEM string into a private key.
112    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
113        // Crack open the PEM to get to the private key info
114        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        // Parse the DER content
119        Self::from_der(&data)
120    }
121
122    /// to_bytes converts a secret key into a 64-byte array.
123    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    /// to_der serializes a private key into a DER buffer.
131    pub fn to_der(&self) -> Vec<u8> {
132        // Create the MLDSA65-Ed25519-SHA512 algorithm identifier; parameters
133        // MUST be absent
134        let alg = pkcs8::AlgorithmIdentifierRef {
135            oid: OID,
136            parameters: None,
137        };
138        // The private key is ML-DSA seed (32) || Ed25519 seed (32) = 64 bytes
139        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    /// to_pem serializes a private key into a PEM string.
150    pub fn to_pem(&self) -> String {
151        pem::encode("PRIVATE KEY", &self.to_der())
152    }
153
154    /// public_key retrieves the public counterpart of the secret key.
155    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    /// fingerprint returns a 256bit unique identifier for this key.
163    pub fn fingerprint(&self) -> Fingerprint {
164        self.public_key().fingerprint()
165    }
166
167    /// sign creates a digital signature of the message.
168    pub fn sign(&self, message: &[u8]) -> Signature {
169        let m_prime = split_signing_message(message);
170
171        // Sign M' with both algorithms
172        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/// PublicKey is an ML-DSA-65 public key paired with an Ed25519 public key for
180/// verifying quantum resistant digital signatures.
181#[derive(Debug, Clone)]
182pub struct PublicKey {
183    ml_key: mldsa::PublicKey,
184    ed_key: eddsa::PublicKey,
185}
186
187impl PublicKey {
188    /// compose creates a public key from its constituent ML-DSA-65 and Ed25519
189    /// public keys.
190    pub fn compose(ml_key: mldsa::PublicKey, ed_key: eddsa::PublicKey) -> Self {
191        Self { ml_key, ed_key }
192    }
193
194    /// split decomposes a public key into its constituent ML-DSA-65 and Ed25519
195    /// public keys.
196    pub fn split(self) -> (mldsa::PublicKey, eddsa::PublicKey) {
197        (self.ml_key, self.ed_key)
198    }
199
200    /// from_bytes converts a 1984-byte array into a public key.
201    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    /// from_der parses a DER buffer into a public key.
212    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
213        // Parse the DER encoded container
214        let info: SubjectPublicKeyInfo<AlgorithmIdentifier<AnyRef>, BitStringRef> =
215            SubjectPublicKeyInfo::from_der(der)?;
216
217        // Reject trailing data by verifying re-encoded length matches input
218        if info.encoded_len()?.try_into() != Ok(der.len()) {
219            return Err("trailing data in public key".into());
220        }
221        // Ensure the algorithm OID matches MLDSA65-Ed25519-SHA512
222        if info.algorithm.oid != OID {
223            return Err("not a composite ML-DSA-65-Ed25519-SHA512 public key".into());
224        }
225        // Public key is ML-DSA-65 (1952 bytes) || Ed25519 (32 bytes) = 1984 bytes
226        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    /// from_pem parses a PEM string into a public key.
237    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
238        // Crack open the PEM to get to the public key info
239        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        // Parse the DER content
244        Self::from_der(&data)
245    }
246
247    /// to_bytes converts a public key into a 1984-byte array.
248    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    /// to_der serializes a public key into a DER buffer.
256    pub fn to_der(&self) -> Vec<u8> {
257        // Create the MLDSA65-Ed25519-SHA512 algorithm identifier; parameters
258        // MUST be absent
259        let alg = spki::AlgorithmIdentifierRef {
260            oid: OID,
261            parameters: None,
262        };
263        // The public key info is the BITSTRING of the two keys concatenated
264        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    /// to_pem serializes a public key into a PEM string.
274    pub fn to_pem(&self) -> String {
275        pem::encode("PUBLIC KEY", &self.to_der())
276    }
277
278    /// fingerprint returns a 256bit unique identifier for this key.
279    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    /// verify verifies a digital signature.
287    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), Box<dyn Error>> {
288        // Construct M' = Prefix || Label || len(ctx) || ctx || PH(M)
289        // where ctx is empty and PH is SHA512
290        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); // len(ctx) = 0, no ctx bytes follow
299        m_prime.extend_from_slice(&prehash);
300
301        // Split and verify both signatures
302        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
350/// split_signing_message derives the composite message M' from a raw message
351/// according to the IETF composite signature spec:
352///
353///   M' = Prefix || Label || len(ctx) || ctx || PH(M)
354///     where ctx is empty and PH is SHA512.
355///
356/// Use this when signing separately with individual ML-DSA and Ed25519 keys
357/// before composing the signatures.
358pub 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); // len(ctx) = 0, no ctx bytes follow
367    m_prime.extend_from_slice(&prehash);
368    m_prime
369}
370
371/// Signature is an ML-DSA-65 signature paired with an Ed25519 signature.
372#[derive(Debug, Clone, PartialEq, Eq)]
373pub struct Signature {
374    ml_sig: mldsa::Signature,
375    ed_sig: eddsa::Signature,
376}
377
378impl Signature {
379    /// compose creates a signature from its constituent ML-DSA-65 and Ed25519
380    /// signatures.
381    pub fn compose(ml_sig: mldsa::Signature, ed_sig: eddsa::Signature) -> Self {
382        Self { ml_sig, ed_sig }
383    }
384
385    /// split decomposes a signature into its constituent ML-DSA-65 and Ed25519
386    /// signatures.
387    pub fn split(&self) -> (mldsa::Signature, eddsa::Signature) {
388        (self.ml_sig.clone(), self.ed_sig)
389    }
390
391    /// from_bytes converts a 3373-byte array into a signature.
392    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    /// to_bytes converts a signature into a 3373-byte array.
403    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/// Fingerprint contains a 256-bit unique identifier for a composite ML-DSA-65-Ed25519-SHA512 key.
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub struct Fingerprint([u8; FINGERPRINT_SIZE]);
453
454impl Fingerprint {
455    /// from_bytes creates a fingerprint from a 32-byte array.
456    pub fn from_bytes(bytes: &[u8; FINGERPRINT_SIZE]) -> Self {
457        Self(*bytes)
458    }
459
460    /// to_bytes converts a fingerprint into a 32-byte array.
461    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    // Test vectors from draft-ietf-lamps-pq-composite-sigs-latest Appendix E
510    // https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs
511    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    // Tests operations with IETF test vectors.
710    #[test]
711    fn test_ietf_vectors() {
712        // Parse the secret key seed (ML-DSA seed || Ed25519 seed)
713        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        // Verify the public key matches (ML-DSA || Ed25519)
718        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        // Sign and verify (ML-DSA uses randomized signing, so we can't compare bytes)
723        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        // Verify the IETF test signature can be verified
729        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        // Parse the secret key from seed and from PKCS8
740        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        // Both should produce the same public key
748        assert_eq!(
749            seckey_from_seed.public_key().to_bytes(),
750            seckey_from_pkcs8.public_key().to_bytes()
751        );
752
753        // Re-encoding to DER should match the test vector
754        let encoded_der = seckey_from_seed.to_der();
755        assert_eq!(encoded_der, pkcs8_bytes);
756
757        // Round-trip: decode and re-encode
758        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        // Sign M' separately with each key
834        let ml_sig = ml_sec.sign(&m_prime, SIGNATURE_DOMAIN);
835        let ed_sig = ed_sec.sign(&m_prime);
836
837        // Compose the signatures and public keys
838        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        // Verify the composed signature with the composed public key
842        composite_pub.verify(message, &composite_sig).unwrap();
843    }
844}