1use crate::error::Error as SignError;
13#[cfg(not(feature = "std"))]
14use alloc::{format, string::ToString, vec::Vec};
15use core::{fmt, marker::PhantomData};
16use dcrypt_api::{Result as ApiResult, SecretVec, Signature as SignatureTrait, ZeroizingBytes};
17use dcrypt_internal::{
18 try_fill_bytes_zeroing_on_error, CryptoRng, RngCore, Zeroize, ZeroizeOnDrop, Zeroizing,
19};
20use dcrypt_params::pqc::ml_dsa::{MlDsa44Params, MlDsa65Params, MlDsa87Params, MlDsaSchemeParams};
21
22mod arithmetic;
23mod encoding;
24mod polyvec;
25mod sampling;
26mod sign;
27
28#[derive(Clone, Debug)]
30pub struct MlDsaPublicKey(pub(crate) Vec<u8>);
31
32#[derive(Clone)]
38pub struct MlDsaSecretKey {
39 bytes: SecretVec,
40 public_key: Option<Vec<u8>>,
41}
42
43#[derive(Clone, Debug)]
45pub struct MlDsaSignature(pub(crate) Vec<u8>);
46
47impl Zeroize for MlDsaSecretKey {
48 fn zeroize(&mut self) {
49 self.bytes.zeroize();
50 self.public_key.zeroize();
51 }
52}
53
54impl Drop for MlDsaSecretKey {
55 fn drop(&mut self) {
56 self.zeroize();
57 }
58}
59
60impl ZeroizeOnDrop for MlDsaSecretKey {}
61
62impl fmt::Debug for MlDsaSecretKey {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.debug_struct("MlDsaSecretKey")
65 .field("bytes", &"[REDACTED]")
66 .finish()
67 }
68}
69
70impl AsRef<[u8]> for MlDsaPublicKey {
71 fn as_ref(&self) -> &[u8] {
72 &self.0
73 }
74}
75
76impl AsMut<[u8]> for MlDsaPublicKey {
77 fn as_mut(&mut self) -> &mut [u8] {
78 &mut self.0
79 }
80}
81
82impl AsRef<[u8]> for MlDsaSecretKey {
83 fn as_ref(&self) -> &[u8] {
84 &self.bytes
85 }
86}
87
88impl AsRef<[u8]> for MlDsaSignature {
89 fn as_ref(&self) -> &[u8] {
90 &self.0
91 }
92}
93
94impl AsMut<[u8]> for MlDsaSignature {
95 fn as_mut(&mut self) -> &mut [u8] {
96 &mut self.0
97 }
98}
99
100impl MlDsaSecretKey {
101 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignError> {
106 let public_key = match bytes.len() {
107 2560 => MlDsa44Params::validate_secret_key(bytes)?,
108 4032 => MlDsa65Params::validate_secret_key(bytes)?,
109 4896 => MlDsa87Params::validate_secret_key(bytes)?,
110 _ => {
111 return Err(SignError::Deserialization(format!(
112 "invalid ML-DSA expanded private key size: {} bytes",
113 bytes.len()
114 )))
115 }
116 };
117
118 Ok(Self {
119 bytes: SecretVec::from_slice(bytes),
120 public_key: Some(public_key),
121 })
122 }
123
124 pub fn from_bytes_with_public_key(
129 bytes: &[u8],
130 public_key: &MlDsaPublicKey,
131 ) -> Result<Self, SignError> {
132 match bytes.len() {
133 2560 => MlDsa44Params::validate_key_pair(bytes, public_key.as_ref())?,
134 4032 => MlDsa65Params::validate_key_pair(bytes, public_key.as_ref())?,
135 4896 => MlDsa87Params::validate_key_pair(bytes, public_key.as_ref())?,
136 _ => {
137 return Err(SignError::Deserialization(format!(
138 "invalid ML-DSA expanded private key size: {} bytes",
139 bytes.len()
140 )))
141 }
142 }
143
144 Ok(Self {
145 bytes: SecretVec::from_slice(bytes),
146 public_key: Some(public_key.as_ref().to_vec()),
147 })
148 }
149
150 pub fn to_bytes(&self) -> &[u8] {
152 &self.bytes
153 }
154
155 pub fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
157 self.bytes.to_bytes_zeroizing_boxed()
158 }
159
160 pub fn public_key(&self) -> Result<MlDsaPublicKey, SignError> {
162 self.public_key
163 .as_ref()
164 .cloned()
165 .map(MlDsaPublicKey)
166 .ok_or_else(|| {
167 SignError::InvalidKey(
168 "public-key derivation is unavailable for an unpaired imported ML-DSA expanded key; import with from_bytes_with_public_key"
169 .to_string(),
170 )
171 })
172 }
173}
174
175impl MlDsaPublicKey {
176 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignError> {
178 match bytes.len() {
179 1312 => MlDsa44Params::validate_public_key(bytes)?,
180 1952 => MlDsa65Params::validate_public_key(bytes)?,
181 2592 => MlDsa87Params::validate_public_key(bytes)?,
182 _ => {
183 return Err(SignError::Deserialization(format!(
184 "invalid ML-DSA public key size: {} bytes",
185 bytes.len()
186 )))
187 }
188 }
189
190 Ok(Self(bytes.to_vec()))
191 }
192
193 pub fn to_bytes(&self) -> &[u8] {
195 &self.0
196 }
197}
198
199impl MlDsaSignature {
200 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignError> {
205 match bytes.len() {
206 2420 => validate_hint_encoding(bytes, 32 + 4 * 576, 80, 4)?,
207 3309 => validate_hint_encoding(bytes, 48 + 5 * 640, 55, 6)?,
208 4627 => validate_hint_encoding(bytes, 64 + 7 * 640, 75, 8)?,
209 _ => {
210 return Err(SignError::InvalidSignatureSize {
211 expected: 0,
212 actual: bytes.len(),
213 })
214 }
215 }
216
217 Ok(Self(bytes.to_vec()))
218 }
219
220 pub fn to_bytes(&self) -> &[u8] {
222 &self.0
223 }
224}
225
226fn validate_hint_encoding(
227 signature: &[u8],
228 hint_offset: usize,
229 omega: usize,
230 k: usize,
231) -> Result<(), SignError> {
232 let hint = signature
233 .get(hint_offset..)
234 .ok_or_else(|| SignError::Deserialization("truncated ML-DSA hint".to_string()))?;
235 if hint.len() != omega + k {
236 return Err(SignError::Deserialization(
237 "invalid ML-DSA hint length".to_string(),
238 ));
239 }
240
241 let (indices, boundaries) = hint.split_at(omega);
242 let mut start = 0usize;
243 for &boundary in boundaries {
244 let end = usize::from(boundary);
245 if end < start || end > omega {
246 return Err(SignError::Deserialization(
247 "non-monotonic ML-DSA hint boundaries".to_string(),
248 ));
249 }
250 if !indices[start..end].windows(2).all(|pair| pair[0] < pair[1]) {
251 return Err(SignError::Deserialization(
252 "duplicate or unsorted ML-DSA hint indices".to_string(),
253 ));
254 }
255 start = end;
256 }
257
258 if indices[start..].iter().any(|&byte| byte != 0) {
259 return Err(SignError::Deserialization(
260 "nonzero unused ML-DSA hint bytes".to_string(),
261 ));
262 }
263
264 Ok(())
265}
266
267#[doc(hidden)]
270pub trait MlDsaBackend: MlDsaSchemeParams + Sized {
271 fn validate_public_key(bytes: &[u8]) -> Result<(), SignError> {
272 encoding::unpack_public_key::<Self>(bytes).map(|_| ())
273 }
274
275 fn validate_secret_key(bytes: &[u8]) -> Result<Vec<u8>, SignError> {
276 sign::validate_secret_key_internal::<Self>(bytes)
277 }
278
279 fn validate_key_pair(secret_key: &[u8], public_key: &[u8]) -> Result<(), SignError> {
280 Self::validate_public_key(public_key)?;
281 sign::validate_key_pair_internal::<Self>(secret_key, public_key)
282 }
283
284 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> Result<(Vec<u8>, SecretVec), SignError> {
285 sign::keypair_internal::<Self, R>(rng)
286 }
287
288 fn sign_internal(
289 formatted_message: &[u8],
290 secret_key: &[u8],
291 randomizer: &[u8; 32],
292 supplied_mu: Option<&[u8; 64]>,
293 ) -> Result<Vec<u8>, SignError> {
294 sign::sign_internal::<Self>(formatted_message, secret_key, randomizer, supplied_mu)
295 }
296
297 fn verify_internal(
298 formatted_message: &[u8],
299 signature: &[u8],
300 public_key: &[u8],
301 supplied_mu: Option<&[u8; 64]>,
302 ) -> Result<(), SignError> {
303 sign::verify_internal::<Self>(formatted_message, signature, public_key, supplied_mu)
304 }
305}
306
307impl MlDsaBackend for MlDsa44Params {}
308impl MlDsaBackend for MlDsa65Params {}
309impl MlDsaBackend for MlDsa87Params {}
310
311pub struct MlDsa<P: MlDsaSchemeParams + 'static> {
313 _params: PhantomData<P>,
314}
315
316fn format_pure_message(message: &[u8], context: &[u8]) -> Result<Vec<u8>, SignError> {
317 if context.len() > u8::MAX as usize {
318 return Err(SignError::InvalidParameter(format!(
319 "ML-DSA context is {} bytes; maximum is 255",
320 context.len()
321 )));
322 }
323 let mut formatted = Vec::with_capacity(2 + context.len() + message.len());
324 formatted.push(0);
325 formatted.push(context.len() as u8);
326 formatted.extend_from_slice(context);
327 formatted.extend_from_slice(message);
328 Ok(formatted)
329}
330
331impl<P> MlDsa<P>
332where
333 P: MlDsaBackend + Send + Sync + 'static,
334{
335 pub fn sign_with_rng<R: CryptoRng + RngCore>(
337 message: &[u8],
338 secret_key: &MlDsaSecretKey,
339 rng: &mut R,
340 ) -> ApiResult<MlDsaSignature> {
341 Self::sign_with_context_rng(message, &[], secret_key, rng)
342 }
343
344 pub fn sign_with_context_rng<R: CryptoRng + RngCore>(
346 message: &[u8],
347 context: &[u8],
348 secret_key: &MlDsaSecretKey,
349 rng: &mut R,
350 ) -> ApiResult<MlDsaSignature> {
351 let formatted = format_pure_message(message, context).map_err(dcrypt_api::Error::from)?;
352 let mut randomizer = Zeroizing::new([0u8; 32]);
353 try_fill_bytes_zeroing_on_error(rng, &mut *randomizer)
354 .map_err(|error| dcrypt_api::Error::from(SignError::Rng(error.to_string())))?;
355 let result = P::sign_internal(&formatted, secret_key.as_ref(), &randomizer, None)
356 .map(MlDsaSignature)
357 .map_err(dcrypt_api::Error::from);
358 result
359 }
360
361 pub fn sign_deterministic(
363 message: &[u8],
364 secret_key: &MlDsaSecretKey,
365 ) -> ApiResult<MlDsaSignature> {
366 Self::sign_deterministic_with_context(message, &[], secret_key)
367 }
368
369 pub fn sign_deterministic_with_context(
371 message: &[u8],
372 context: &[u8],
373 secret_key: &MlDsaSecretKey,
374 ) -> ApiResult<MlDsaSignature> {
375 let formatted = format_pure_message(message, context).map_err(dcrypt_api::Error::from)?;
376 let signature = P::sign_internal(&formatted, secret_key.as_ref(), &[0u8; 32], None)
377 .map_err(dcrypt_api::Error::from)?;
378 Ok(MlDsaSignature(signature))
379 }
380
381 pub fn verify_with_context(
383 message: &[u8],
384 context: &[u8],
385 signature: &MlDsaSignature,
386 public_key: &MlDsaPublicKey,
387 ) -> ApiResult<()> {
388 validate_hint_encoding_for_len(signature.as_ref()).map_err(dcrypt_api::Error::from)?;
389 let formatted = format_pure_message(message, context).map_err(dcrypt_api::Error::from)?;
390 P::verify_internal(&formatted, signature.as_ref(), public_key.as_ref(), None)
391 .map_err(dcrypt_api::Error::from)
392 }
393
394 #[doc(hidden)]
396 pub fn sign_internal_with_randomizer(
397 formatted_message: &[u8],
398 secret_key: &MlDsaSecretKey,
399 randomizer: &[u8; 32],
400 ) -> ApiResult<MlDsaSignature> {
401 P::sign_internal(formatted_message, secret_key.as_ref(), randomizer, None)
402 .map(MlDsaSignature)
403 .map_err(dcrypt_api::Error::from)
404 }
405
406 #[doc(hidden)]
408 pub fn sign_mu_with_randomizer(
409 mu: &[u8; 64],
410 secret_key: &MlDsaSecretKey,
411 randomizer: &[u8; 32],
412 ) -> ApiResult<MlDsaSignature> {
413 P::sign_internal(&[], secret_key.as_ref(), randomizer, Some(mu))
414 .map(MlDsaSignature)
415 .map_err(dcrypt_api::Error::from)
416 }
417
418 #[doc(hidden)]
420 pub fn verify_internal_message(
421 formatted_message: &[u8],
422 signature: &MlDsaSignature,
423 public_key: &MlDsaPublicKey,
424 ) -> ApiResult<()> {
425 validate_hint_encoding_for_len(signature.as_ref()).map_err(dcrypt_api::Error::from)?;
426 P::verify_internal(
427 formatted_message,
428 signature.as_ref(),
429 public_key.as_ref(),
430 None,
431 )
432 .map_err(dcrypt_api::Error::from)
433 }
434
435 #[doc(hidden)]
437 pub fn verify_mu(
438 mu: &[u8; 64],
439 signature: &MlDsaSignature,
440 public_key: &MlDsaPublicKey,
441 ) -> ApiResult<()> {
442 validate_hint_encoding_for_len(signature.as_ref()).map_err(dcrypt_api::Error::from)?;
443 P::verify_internal(&[], signature.as_ref(), public_key.as_ref(), Some(mu))
444 .map_err(dcrypt_api::Error::from)
445 }
446}
447
448impl<P> SignatureTrait for MlDsa<P>
449where
450 P: MlDsaBackend + Send + Sync + 'static,
451{
452 type PublicKey = MlDsaPublicKey;
453 type SecretKey = MlDsaSecretKey;
454 type SignatureData = MlDsaSignature;
455 type KeyPair = (Self::PublicKey, Self::SecretKey);
456
457 fn name() -> &'static str {
458 P::NAME
459 }
460
461 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
462 let (public, secret) = P::keypair(rng).map_err(dcrypt_api::Error::from)?;
463 Ok((
464 MlDsaPublicKey(public.clone()),
465 MlDsaSecretKey {
466 bytes: secret,
467 public_key: Some(public),
468 },
469 ))
470 }
471
472 fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
473 keypair.0.clone()
474 }
475
476 fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
477 keypair.1.clone()
478 }
479
480 fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
481 Self::sign_deterministic(message, secret_key)
482 }
483
484 fn verify(
485 message: &[u8],
486 signature: &Self::SignatureData,
487 public_key: &Self::PublicKey,
488 ) -> ApiResult<()> {
489 Self::verify_with_context(message, &[], signature, public_key)
490 }
491}
492
493fn validate_hint_encoding_for_len(signature: &[u8]) -> Result<(), SignError> {
494 match signature.len() {
495 2420 => validate_hint_encoding(signature, 32 + 4 * 576, 80, 4),
496 3309 => validate_hint_encoding(signature, 48 + 5 * 640, 55, 6),
497 4627 => validate_hint_encoding(signature, 64 + 7 * 640, 75, 8),
498 actual => Err(SignError::InvalidSignatureSize {
499 expected: 0,
500 actual,
501 }),
502 }
503}
504
505pub type MlDsa44 = MlDsa<MlDsa44Params>;
507pub type MlDsa65 = MlDsa<MlDsa65Params>;
509pub type MlDsa87 = MlDsa<MlDsa87Params>;
511
512#[cfg(test)]
513mod tests;