1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
5#![deny(missing_docs)]
6
7#[cfg(not(feature = "std"))]
8extern crate alloc;
9
10pub mod public;
12#[cfg(feature = "std")]
13use std::{format, string::String, vec, vec::Vec};
14
15#[cfg(not(feature = "std"))]
16use alloc::{format, string::String, vec, vec::Vec};
17use core::{convert::TryFrom, fmt, mem, str::FromStr};
18use ml_dsa::{
19 EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, KeyGen, MlDsa87,
20 Signature as MlSignature, SigningKey, VerifyingKey,
21};
22use pkcs8::spki::EncodePublicKey;
23pub use public::{
24 kid_from_spki_der, spki_der_canonical, spki_mldsa_paramset, spki_subject_key_bytes,
25};
26use qs_drbg::rand_adapter::DrbgRng;
27use qs_drbg::{Error as InnerError, HmacDrbg};
28use sha2::{Digest, Sha256};
29use zeroize::{Zeroize, ZeroizeOnDrop};
30
31pub mod mldsa87 {
33 pub const PUBLIC_KEY_LEN: usize = 2592;
35 pub const SECRET_KEY_LEN: usize = 4896;
37 pub const SIGNATURE_LEN: usize = 4627;
39}
40
41pub const MLDSA87_SECRET_KEY_LEN: usize = mem::size_of::<EncodedSigningKey<MlDsa87>>();
43pub const MLDSA87_PUBLIC_KEY_LEN: usize = mem::size_of::<EncodedVerifyingKey<MlDsa87>>();
45pub const MLDSA87_SIGNATURE_LEN: usize = mem::size_of::<EncodedSignature<MlDsa87>>();
47
48const SIGNING_CONTEXT: &[u8] = b"quantum-sign.v1";
49const TRANSCRIPT_DOMAIN: &[u8] = b"quantum-sign:v1";
51pub const TRANSCRIPT_DIGEST_LEN: usize = 32;
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum DigestAlg {
57 Sha256,
59 Sha512,
61 Shake256_64,
63}
64
65impl DigestAlg {
66 pub fn as_str(self) -> &'static str {
68 match self {
69 DigestAlg::Sha256 => "sha256",
70 DigestAlg::Sha512 => "sha512",
71 DigestAlg::Shake256_64 => "shake256-64",
72 }
73 }
74
75 pub fn output_len(self) -> usize {
77 match self {
78 DigestAlg::Sha256 => 32,
79 DigestAlg::Sha512 | DigestAlg::Shake256_64 => 64,
80 }
81 }
82}
83
84impl FromStr for DigestAlg {
85 type Err = &'static str;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 match s.to_ascii_lowercase().as_str() {
89 "sha256" => Ok(DigestAlg::Sha256),
90 "sha512" => Ok(DigestAlg::Sha512),
91 "shake256-64" | "shake256" => Ok(DigestAlg::Shake256_64),
92 _ => Err("unsupported digest algorithm"),
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum DrbgError {
100 RequestTooLarge,
102 ReseedRequired,
104 EntropyUnavailable,
106 EntropyHealthFailed,
108}
109
110impl fmt::Display for DrbgError {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 DrbgError::RequestTooLarge => write!(f, "DRBG request exceeds per-call limit"),
114 DrbgError::ReseedRequired => write!(f, "DRBG reseed required"),
115 DrbgError::EntropyUnavailable => write!(f, "OS entropy unavailable"),
116 DrbgError::EntropyHealthFailed => write!(f, "entropy health check failed"),
117 }
118 }
119}
120
121#[cfg(feature = "std")]
122impl std::error::Error for DrbgError {}
123
124impl From<InnerError> for DrbgError {
125 fn from(value: InnerError) -> Self {
126 match value {
127 InnerError::RequestTooLarge => DrbgError::RequestTooLarge,
128 InnerError::ReseedRequired => DrbgError::ReseedRequired,
129 InnerError::EntropyUnavailable => DrbgError::EntropyUnavailable,
130 InnerError::EntropyHealthFailed => DrbgError::EntropyHealthFailed,
131 }
132 }
133}
134
135pub fn transcript_digest(
137 sign_alg: &str,
138 digest_alg: &str,
139 message_digest: &[u8],
140 policy_hash: Option<&[u8]>,
141) -> [u8; TRANSCRIPT_DIGEST_LEN] {
142 let mut hasher = Sha256::new();
143 hasher.update(TRANSCRIPT_DOMAIN);
144 hasher.update(b"|alg:");
145 hasher.update(sign_alg.as_bytes());
146 hasher.update(b"|hash:");
147 hasher.update(digest_alg.as_bytes());
148 if let Some(ph) = policy_hash {
149 hasher.update(b"|policy:");
150 hasher.update(ph);
151 }
152 hasher.update(b"|msg:");
153 hasher.update(message_digest);
154 let digest = hasher.finalize();
155 let mut out = [0u8; TRANSCRIPT_DIGEST_LEN];
156 out.copy_from_slice(&digest);
157 out
158}
159
160pub trait DeterministicRng {
162 fn fill_bytes(&mut self, out: &mut [u8]) -> Result<(), DrbgError>;
164
165 fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), DrbgError>;
167
168 fn set_reseed_interval(&mut self, interval: u64);
170
171 fn set_max_bytes_between_reseed(&mut self, bytes: u128);
173}
174
175#[derive(Zeroize, ZeroizeOnDrop)]
177pub struct HmacSha512Drbg {
178 inner: HmacDrbg,
179}
180
181impl HmacSha512Drbg {
182 pub fn new(
184 entropy: &[u8],
185 nonce: &[u8],
186 personalization: Option<&[u8]>,
187 ) -> Result<Self, DrbgError> {
188 let inner = HmacDrbg::new(entropy, nonce, personalization).map_err(DrbgError::from)?;
189 Ok(Self { inner })
190 }
191
192 pub fn from_os(personalization: Option<&[u8]>) -> Result<Self, DrbgError> {
194 let inner = HmacDrbg::from_os(personalization).map_err(DrbgError::from)?;
195 Ok(Self { inner })
196 }
197
198 pub fn inner_mut(&mut self) -> &mut HmacDrbg {
200 &mut self.inner
201 }
202
203 #[cfg(test)]
205 pub fn inner(&self) -> &HmacDrbg {
206 &self.inner
207 }
208}
209
210impl fmt::Debug for HmacSha512Drbg {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.debug_struct("HmacSha512Drbg").finish_non_exhaustive()
213 }
214}
215
216impl DeterministicRng for HmacSha512Drbg {
217 fn fill_bytes(&mut self, out: &mut [u8]) -> Result<(), DrbgError> {
218 self.inner.generate(out, None).map_err(DrbgError::from)
219 }
220
221 fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), DrbgError> {
222 self.inner
223 .reseed(entropy, additional_input)
224 .map_err(DrbgError::from)
225 }
226
227 fn set_reseed_interval(&mut self, interval: u64) {
228 self.inner.set_reseed_interval(interval);
229 }
230
231 fn set_max_bytes_between_reseed(&mut self, bytes: u128) {
232 self.inner.set_max_bytes_between_reseed(bytes);
233 }
234}
235
236pub fn random_bytes(len: usize) -> Result<Vec<u8>, DrbgError> {
238 let mut drbg = HmacSha512Drbg::from_os(None)?;
239 let mut buf = vec![0u8; len];
240 drbg.fill_bytes(&mut buf)?;
241 Ok(buf)
242}
243
244#[derive(Debug, Clone)]
246pub struct Keypair {
247 pub secret: Vec<u8>,
249 pub public: Vec<u8>,
251}
252
253impl Drop for Keypair {
254 fn drop(&mut self) {
255 self.zeroize();
256 }
257}
258
259impl Zeroize for Keypair {
260 fn zeroize(&mut self) {
261 self.secret.zeroize();
262 self.public.zeroize();
263 }
264}
265
266impl ZeroizeOnDrop for Keypair {}
267
268#[derive(Debug)]
270pub enum CryptoError {
271 Drbg(DrbgError),
273 InvalidKey,
275 InvalidSignature,
277 SigningFailed,
279 BadDigestLen {
281 expected: usize,
283 got: usize,
285 },
286 PublicKey(String),
288}
289
290impl fmt::Display for CryptoError {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 match self {
293 CryptoError::Drbg(err) => write!(f, "{err}"),
294 CryptoError::InvalidKey => write!(f, "invalid key material"),
295 CryptoError::InvalidSignature => write!(f, "invalid signature"),
296 CryptoError::SigningFailed => write!(f, "signature generation failed"),
297 CryptoError::BadDigestLen { expected, got } => {
298 write!(f, "bad digest length: expected {expected}, got {got}")
299 }
300 CryptoError::PublicKey(msg) => write!(f, "public key error: {msg}"),
301 }
302 }
303}
304
305impl From<DrbgError> for CryptoError {
306 fn from(err: DrbgError) -> Self {
307 CryptoError::Drbg(err)
308 }
309}
310
311#[cfg(feature = "std")]
312impl std::error::Error for CryptoError {}
313
314pub fn keypair_mldsa87(drbg: &mut HmacSha512Drbg) -> Result<Keypair, CryptoError> {
316 let mut rng = DrbgRng::new(drbg.inner_mut());
317 let kp = MlDsa87::key_gen(&mut rng);
318 let sk = kp.signing_key().encode().to_vec();
319 let pk = kp.verifying_key().encode().to_vec();
320 Ok(Keypair {
321 secret: sk,
322 public: pk,
323 })
324}
325
326pub fn sign_mldsa87(
328 drbg: &mut HmacSha512Drbg,
329 secret_key: &[u8],
330 message_digest: &[u8],
331 digest_alg: DigestAlg,
332 policy_hash: Option<&[u8]>,
333) -> Result<Vec<u8>, CryptoError> {
334 if message_digest.len() != digest_alg.output_len() {
335 return Err(CryptoError::BadDigestLen {
336 expected: digest_alg.output_len(),
337 got: message_digest.len(),
338 });
339 }
340 let enc =
341 EncodedSigningKey::<MlDsa87>::try_from(secret_key).map_err(|_| CryptoError::InvalidKey)?;
342 let sk = SigningKey::<MlDsa87>::decode(&enc);
343 let transcript =
344 transcript_digest("mldsa-87", digest_alg.as_str(), message_digest, policy_hash);
345 drbg.inner_mut()
346 .generate(&mut [], Some(&transcript))
347 .map_err(DrbgError::from)?;
348 let mut rng = DrbgRng::new(drbg.inner_mut());
349 let sig = sk
350 .sign_randomized(transcript.as_slice(), SIGNING_CONTEXT, &mut rng)
351 .map_err(|_| CryptoError::SigningFailed)?;
352 Ok(sig.encode().to_vec())
353}
354
355pub fn verify_mldsa87(
357 public_key: &[u8],
358 message_digest: &[u8],
359 digest_alg: DigestAlg,
360 signature: &[u8],
361 policy_hash: Option<&[u8]>,
362) -> Result<(), CryptoError> {
363 if message_digest.len() != digest_alg.output_len() {
364 return Err(CryptoError::BadDigestLen {
365 expected: digest_alg.output_len(),
366 got: message_digest.len(),
367 });
368 }
369 if public_key.len() != mldsa87::PUBLIC_KEY_LEN {
370 return Err(CryptoError::InvalidSignature);
371 }
372 if signature.len() != mldsa87::SIGNATURE_LEN {
373 return Err(CryptoError::InvalidSignature);
374 }
375 let enc_vk = EncodedVerifyingKey::<MlDsa87>::try_from(public_key)
376 .map_err(|_| CryptoError::InvalidKey)?;
377 let vk = VerifyingKey::<MlDsa87>::decode(&enc_vk);
378 let enc_sig = EncodedSignature::<MlDsa87>::try_from(signature)
379 .map_err(|_| CryptoError::InvalidSignature)?;
380 let sig = MlSignature::<MlDsa87>::decode(&enc_sig).ok_or(CryptoError::InvalidSignature)?;
381 let transcript =
382 transcript_digest("mldsa-87", digest_alg.as_str(), message_digest, policy_hash);
383 if vk.verify_with_context(transcript.as_slice(), SIGNING_CONTEXT, &sig) {
384 Ok(())
385 } else {
386 Err(CryptoError::InvalidSignature)
387 }
388}
389
390pub fn kid_from_public_key(public_key: &[u8]) -> Result<String, CryptoError> {
392 let spki = public_key_to_spki(public_key)?;
393 Ok(public::kid_from_spki_der(&spki))
394}
395
396pub fn public_key_to_spki(public_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
398 let enc_vk = EncodedVerifyingKey::<MlDsa87>::try_from(public_key)
399 .map_err(|_| CryptoError::InvalidKey)?;
400 let vk = VerifyingKey::<MlDsa87>::decode(&enc_vk);
401 let spki = vk
402 .to_public_key_der()
403 .map_err(|_| CryptoError::InvalidKey)?;
404 Ok(spki.as_bytes().to_vec())
405}
406
407pub fn verify_mldsa87_spki(
409 spki_der: &[u8],
410 message_digest: &[u8],
411 digest_alg: DigestAlg,
412 signature: &[u8],
413 policy_hash: Option<&[u8]>,
414) -> Result<(), CryptoError> {
415 let public_key =
416 spki_subject_key_bytes(spki_der).map_err(|e| CryptoError::PublicKey(format!("{e}")))?;
417 if public_key.len() != mldsa87::PUBLIC_KEY_LEN {
418 return Err(CryptoError::InvalidSignature);
419 }
420 if signature.len() != mldsa87::SIGNATURE_LEN {
421 return Err(CryptoError::InvalidSignature);
422 }
423 verify_mldsa87(
424 &public_key,
425 message_digest,
426 digest_alg,
427 signature,
428 policy_hash,
429 )
430}
431pub fn is_level5_sig_alg(alg: &str) -> bool {
433 matches!(
434 alg,
435 "mldsa-87"
436 | "slh-dsa-sha2-256s"
437 | "slh-dsa-sha2-256f"
438 | "slh-dsa-shake-256s"
439 | "slh-dsa-shake-256f"
440 )
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use sha2::{Digest, Sha512};
447
448 fn entropy(seed: u8) -> [u8; 48] {
449 let mut out = [0u8; 48];
450 for (i, byte) in out.iter_mut().enumerate() {
451 *byte = seed.wrapping_add(i as u8);
452 }
453 out
454 }
455
456 fn nonce(seed: u8) -> [u8; 16] {
457 let mut out = [0u8; 16];
458 for (i, byte) in out.iter_mut().enumerate() {
459 *byte = seed.wrapping_add((i * 5) as u8);
460 }
461 out
462 }
463
464 #[test]
465 fn keygen_and_sign_verify() {
466 let mut drbg = HmacSha512Drbg::new(&entropy(1), &nonce(2), Some(b"keygen")).unwrap();
467 let kp = keypair_mldsa87(&mut drbg).expect("keypair");
468
469 let mut signer_drbg = HmacSha512Drbg::new(&entropy(3), &nonce(4), Some(b"sign")).unwrap();
470 let mut hasher = Sha512::new();
471 hasher.update(b"deterministic digest");
472 let digest = hasher.finalize().to_vec();
473 let sig = sign_mldsa87(
474 &mut signer_drbg,
475 &kp.secret,
476 &digest,
477 DigestAlg::Sha512,
478 None,
479 )
480 .expect("sign");
481
482 verify_mldsa87(&kp.public, &digest, DigestAlg::Sha512, &sig, None).expect("verify");
483
484 let mut bad_hasher = Sha512::new();
485 bad_hasher.update(b"different");
486 let bad_digest = bad_hasher.finalize().to_vec();
487 assert!(verify_mldsa87(&kp.public, &bad_digest, DigestAlg::Sha512, &sig, None).is_err());
488 }
489
490 #[test]
491 fn reject_wrong_digest_length() {
492 let mut drbg = HmacSha512Drbg::new(&entropy(7), &nonce(8), Some(b"keygen")).unwrap();
493 let kp = keypair_mldsa87(&mut drbg).expect("keypair");
494 let mut signer_drbg = HmacSha512Drbg::new(&entropy(9), &nonce(10), Some(b"sign")).unwrap();
495 let short = vec![0u8; DigestAlg::Sha512.output_len() - 1];
496 assert!(matches!(
497 sign_mldsa87(
498 &mut signer_drbg,
499 &kp.secret,
500 &short,
501 DigestAlg::Sha512,
502 None
503 ),
504 Err(CryptoError::BadDigestLen { .. })
505 ));
506 }
507
508 #[test]
509 fn kid_from_public_key_has_expected_length() {
510 let mut drbg = HmacSha512Drbg::new(&entropy(11), &nonce(12), Some(b"keygen")).unwrap();
511 let kp = keypair_mldsa87(&mut drbg).expect("keypair");
512 let kid = kid_from_public_key(&kp.public).expect("kid from public");
513 assert_eq!(kid.len(), 16);
514 }
515
516 #[test]
517 fn verify_rejects_truncated_signature() {
518 let mut drbg = HmacSha512Drbg::new(&entropy(21), &nonce(22), Some(b"keygen")).unwrap();
519 let kp = keypair_mldsa87(&mut drbg).expect("keypair");
520 let mut signer_drbg = HmacSha512Drbg::new(&entropy(23), &nonce(24), Some(b"sign")).unwrap();
521 let digest = vec![0xAB; DigestAlg::Sha512.output_len()];
522 let sig = sign_mldsa87(
523 &mut signer_drbg,
524 &kp.secret,
525 &digest,
526 DigestAlg::Sha512,
527 None,
528 )
529 .expect("sign");
530 assert_eq!(sig.len(), mldsa87::SIGNATURE_LEN);
531
532 let mut truncated = sig.clone();
533 truncated.truncate(truncated.len() - 1);
534 assert!(verify_mldsa87(&kp.public, &digest, DigestAlg::Sha512, &sig, None).is_ok());
535 assert!(verify_mldsa87(&kp.public, &digest, DigestAlg::Sha512, &truncated, None).is_err());
536
537 let spki = public_key_to_spki(&kp.public).expect("spki");
539 assert!(verify_mldsa87_spki(&spki, &digest, DigestAlg::Sha512, &truncated, None).is_err());
540 }
541}