bouncycastle_mldsa_lowmemory/mldsa.rs
1//! This page documents advanced features of the Module Lattice Digital Signature Algorithm (ML-DSA)
2//! available in this crate.
3//!
4//!
5//! # Streaming APIs
6//!
7//! Sometimes the message that needs to be signed or verified is too big to fit in device memory all at once.
8//! No worries, we got you covered!
9//!
10//! ```rust
11//! use bouncycastle_core::errors::SignatureError;
12//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
13//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
14//!
15//! let (pk, sk) = MLDSA65::keygen().unwrap();
16//!
17//! // For illustration purposes, assume that this message was so long that it couldn't possibly
18//! // be streamed in its entirety over a network, and therefore it needs to be pre-hashed.
19//! let msg_chunk1 = b"The quick brown fox ";
20//! let msg_chunk2 = b"jumped over the lazy dog";
21//!
22//! let mut signer = MLDSA65::sign_init(&sk, None).unwrap();
23//! signer.sign_update(msg_chunk1);
24//! signer.sign_update(msg_chunk2);
25//! let sig = signer.sign_final().unwrap();
26//! // This is the signature value that can be saved to a file or whatever it is needed.
27//!
28//! // This is compatible with a verifies that takes the whole message as one chunk:
29//! let msg = b"The quick brown fox jumped over the lazy dog";
30//! match MLDSA65::verify(&pk, msg, None, &sig) {
31//! Ok(()) => println!("Signature is valid!"),
32//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
33//! Err(e) => panic!("Something else went wrong: {:?}", e),
34//! }
35//!
36//! // But of course there's also a streaming API for the verifier!
37//! let mut verifier = MLDSA65::verify_init(&pk, None).unwrap();
38//! verifier.verify_update(msg_chunk1);
39//! verifier.verify_update(msg_chunk2);
40//!
41//! match verifier.verify_final(&sig.as_slice()) {
42//! Ok(()) => println!("Signature is valid!"),
43//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
44//! Err(e) => panic!("Something else went wrong: {:?}", e),
45//! }
46//! ```
47//!
48//!
49//! Note that the streaming API also supports setting the signing context `ctx` and signing nonce `rnd`,
50//! which are explained in more detail below.
51//!
52//! ```rust
53//! use bouncycastle_core::errors::SignatureError;
54//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
55//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
56//!
57//! let (pk, sk) = MLDSA65::keygen().unwrap();
58//!
59//! // For illustration purposes, assume that this message was so long that it couldn't possibly
60//! // be streamed in its entirety over a network, and therefore it needs to be pre-hashed.
61//! let msg_chunk1 = b"The quick brown fox ";
62//! let msg_chunk2 = b"jumped over the lazy dog";
63//!
64//! let mut signer = MLDSA65::sign_init(&sk, Some(b"signing ctx value")).unwrap();
65//! signer.set_signer_rnd([0u8; 32]); // an all-zero rnd is the "deterministic" mode of ML-DSA
66//! signer.sign_update(msg_chunk1);
67//! signer.sign_update(msg_chunk2);
68//! let sig = signer.sign_final().unwrap();
69//! ```
70//!
71//! # External Mu mode
72//!
73//! Here, `mu` refers to the message digest which is computed internally to the ML-DSA algorithm:
74//!
75//! > π β H(BytesToBits(π‘π)||πβ², 64)
76//! > β· message representative that may optionally be computed in a different cryptographic module
77//!
78//! The External Mu mode of ML-DSA fulfills a similar function to [`hash_mldsa`] in that it allows large
79//! messages to be pre-digested outside of the cryptographic module that holds the private key,
80//! but it does it in a way that is compatible with the ML-DSA verification function.
81//! In other works, whereas [`hash_mldsa`] represents a different signature algorithm, the external mu
82//! mode of ML-DSA is simply internal implementation detail of how the signature was computed and
83//! produces signatures that are indistinguishable from "direct" ML-DSA mode.
84//!
85//! The one potential complication with external mu mode -- that [`hash_mldsa`] does not have --
86//! is that it requires the user to know the public key that they are about to sign the message with.
87//! Or, more specifically, the hash of the public key `tr`.
88//! `tr` is a public value (derivable from the public key), so there is no harm in, for example,
89//! sending it down to a client device so that it can pre-hash a large message and only send the
90//! 64-byte `mu` value up to the server to be signed.
91//! But in some contexts, the message has to be pre-hashed for performance reasons but
92//! the public key that will be used for signing cannot be known in advance.
93//! For those use cases, the only choice is to use [`hash_mldsa`].
94//!
95//! This library exposes [`MuBuilder`] which can be used to pre-hash a large to-be-signed message
96//! along with the public key hash `tr`:
97//!
98//! ```rust
99//! use bouncycastle_core::errors::SignatureError;
100//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
101//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
102//!
103//! let (pk, _) = MLDSA65::keygen().unwrap();
104//!
105//! // Let's pretend this message was so long that it couldn't possibly
106//! // streamed in its entirety over a network, and it needs to be pre-hashed.
107//! let msg = b"The quick brown fox jumped over the lazy dog";
108//!
109//! let mu: [u8; 64] = MuBuilder::compute_mu(&pk.compute_tr(), msg, None).unwrap();
110//! ```
111//!
112//! Note: binding a `ctx` value (explained below) needs to be done in [`MuBuilder::compute_mu`].
113//!
114//! If the message really is so huge that it can't all be held in memory at once, then it might
115//! be preferable to use a streaming API for computing mu:
116//!
117//! ```rust
118//! use bouncycastle_core::errors::SignatureError;
119//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
120//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
121//!
122//! let (pk, _) = MLDSA65::keygen().unwrap();
123//!
124//! // Let's pretend this message was so long that it couldn't possibly
125//! // streamed in its entirety over a network, and it needs to be pre-hashed.
126//! let msg_chunk1 = b"The quick brown fox ";
127//! let msg_chunk2 = b"jumped over the lazy dog";
128//!
129//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap();
130//! mb.do_update(msg_chunk1);
131//! mb.do_update(msg_chunk2);
132//! let mu = mb.do_final();
133//! ```
134//!
135//! Given a mu value, it is possible to compute a signature that verifies as normal (no mu's required!):
136//!
137//! ```rust
138//! use bouncycastle_core::errors::SignatureError;
139//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
140//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
141//!
142//! let msg = b"The quick brown fox jumped over the lazy dog";
143//!
144//! let (pk, sk) = MLDSA65::keygen().unwrap();
145//!
146//! // Assume this was computed somewhere else and received by the user.
147//! // Then the sender would have had to know pk!
148//! let mu: [u8; 64] = MuBuilder::compute_mu(&pk.compute_tr(), msg, None).unwrap();
149//!
150//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap();
151//! // This is the signature value that can be saved to a file or whatever it is need.
152//!
153//! match MLDSA65::verify(&pk, msg, None, &sig) {
154//! Ok(()) => println!("Signature is valid!"),
155//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
156//! Err(e) => panic!("Something else went wrong: {:?}", e),
157//! }
158//!
159//! ```
160//!
161//! # Ctx and Rnd params
162//! Various functions in this crate allows setting the signing context value (`ctx`) and the signing nonce (`rnd`).
163//! Here is an overview of both:
164//!
165//! ## ctx
166//! The `ctx` value allows the signer to bind the signature value to an extra piece of information
167//! (up to 255 bytes long) that must also be known to the verifier in order to successfully verify the signature.
168//! This optional parameter allows cryptographic protocol designers to get additional binding properties
169//! from the ML-DSA signature.
170//! The `ctx` value should be something that is known to both the signer and verifier,
171//! does not necessarily need to be a secret, but should not go over the wire as part of the not-yet-verified message.
172//! Examples of uses of the `ctx` could include binding the application data type (ex: `FooEmailData`) in order
173//! to disambiguate other data types that share an encoding (ex: `FooTextDocumentData`) and might otherwise be possible for an
174//! attacker to trick a verifier into accepting one in place of the other.
175//! In a network protocol, `ctx` could be used to bind a transaction ID or protocol nonce in order to strongly
176//! protect against replay attacks.
177//! Generally, it is safe to ignore any property about a `ctx` object that is not well understood.
178//!
179//! Example of signing and verifying with a `ctx` value:
180//!
181//! ```rust
182//! use bouncycastle_core::errors::SignatureError;
183//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
184//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait};
185//!
186//! let msg = b"The quick brown fox";
187//! let ctx = b"FooTextDocumentFormat";
188//!
189//! let (pk, sk) = MLDSA65::keygen().unwrap();
190//!
191//! let sig = MLDSA65::sign(&sk, msg, Some(ctx)).unwrap();
192//! // This is the signature value that can be saved to a file or whatever it is needed.
193//!
194//! match MLDSA65::verify(&pk, msg, Some(ctx), &sig) {
195//! Ok(()) => println!("Signature is valid!"),
196//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
197//! Err(e) => panic!("Something else went wrong: {:?}", e),
198//! }
199//! ```
200//!
201//! ## rnd
202//!
203//! This is the signature nonce, whose purpose is to ensure that every time a signature is computed for the same
204//! message, it results in a different value
205//!
206//! In general, the "deterministic" mode of ML-DSA (which usually uses an all-zero `rnd`) is considered
207//! secure and safe to use, however, certain privacy properties may be lost. For example,
208//! it becomes evident that multiple identical signatures means that the same message was signed multiple times
209//! by the same private key.
210//!
211//! The default mode of ML-DSA uses a `rnd` generated by the library's OS-backed RNG, the `rnd` can be set by the user
212//! if necessary; for example if the function is run on an embedded device that does not have access to an RNG.
213//!
214//! Note that in order to avoid combinatorial explosion of API functions, setting the `rnd` value is only
215//! available in conjunction with external mu or streaming modes. The example of setting `rnd` on the streaming
216//! API was shown above.
217//!
218//! Here is an example of using the [`MLDSA::sign_mu_deterministic`] function:
219//!
220//! ```rust
221//! use bouncycastle_core::errors::SignatureError;
222//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
223//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
224//!
225//! let msg = b"The quick brown fox jumped over the lazy dog";
226//!
227//! let (pk, sk) = MLDSA65::keygen().unwrap();
228//!
229//! // Assume this was computed somewhere else, then
230//! // the party that computed it would have had to know pk
231//! let mu: [u8; 64] = MuBuilder::compute_mu(&pk.compute_tr(), msg, None).unwrap();
232//!
233//! // Typically, "deterministic" mode of ML-DSA will use an all-zero `rnd`,
234//! // but here it is exposed it so it can be set any value, as needed.
235//! let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap();
236//! // This is the signature value that can saved to a file or whatever it is needed.
237//!
238//! match MLDSA65::verify(&pk, msg, None, &sig) {
239//! Ok(()) => println!("Signature is valid!"),
240//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
241//! Err(e) => panic!("Something else went wrong: {:?}", e),
242//! }
243//! ```
244//!
245//! # sign_from_seed
246//!
247//! This mode is intended for users with extreme performance or resource-limitation requirements.
248//!
249//! A very careful analysis of the ML-DSA signing algorithm will show that
250//! the entire ML-DSA private key does not need to be in memory at the same time.
251//! In fact, it is possible to merge the keygen() and sign() functions
252//!
253//! The code provides [`MLDSA::sign_mu_deterministic_from_seed`] which implements such an algorithm.
254//! It has a significantly lower peak-memory-footprint than the regular signing API (although there's
255//! always room for more optimization), and according to our benchmarks it is only around 25% slower
256//! than signing with a fully-expanded private key -- which is still faster than performing a full
257//! keygen followed by a regular sign since there are intermediate values common to keygen and sign
258//! that the merged function is able to only compute once.
259//!
260//! Since this is intended for hard-core embedded systems people, this has not been wrapped in all
261//! the beginner-friendly APIs. It is implied that a user that needs this functionality also knows how
262//! to use it and what they are doing
263//!
264//! Example usage:
265//!
266//! ```rust
267//! use bouncycastle_core::errors::SignatureError;
268//! use bouncycastle_core::traits::{Signer, SignatureVerifier};
269//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait};
270//! use bouncycastle_hex as hex;
271//! use bouncycastle_mldsa_lowmemory::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
272//!
273//! let msg = b"The quick brown fox jumped over the lazy dog";
274//!
275//! let seed = KeyMaterial256::from_bytes_as_type(
276//! &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(),
277//! KeyType::Seed,
278//! ).unwrap();
279//!
280//! // The public key is computed so that the signature can be verified by anyone.
281//! // It also computes the hash `tr` of the public key to later be used to bind the public key at the time of signing.
282//! // There is no short-cut to efficiently computing the public key or `tr` from the seed;
283//! // The full keygen need to be run in order to get the full private key, at least momentarily, then
284//! // it can be discarded and only keep `tr` and `seed`.
285//! let (pk, _) = MLDSA44::keygen_from_seed(&seed).unwrap();
286//! let tr: [u8; 64] = pk.compute_tr();
287//!
288//! // Assume this was computed somewhere else, then
289//! // the party that computed it would have had to know pk
290//! let mu: [u8; 64] = MuBuilder::compute_mu(&tr, msg, None).unwrap();
291//! let rnd: [u8; 32] = [0u8; 32]; // with this API, the user is responsible for their own nonce
292//! // because in the cases where this level of memory optimization
293//! // is needed, our RNG probably won't work anyway.
294//!
295//! let mut sig = [0u8; MLDSA44_SIG_LEN];
296//! let bytes_written = MLDSA44::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, &mut sig).unwrap();
297//!
298//! // it can be verified normally
299//! match MLDSA44::verify(&pk, msg, None, &sig) {
300//! Ok(()) => println!("Signature is valid!"),
301//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
302//! Err(e) => panic!("Something else went wrong: {:?}", e),
303//! }
304//! ```
305//!
306//! While this is currently only supported when operating from a seed-based private key, something analogous
307//! could be done that merges the sk_decode() and sign() routines when working with the standardized
308//! private key encoding (which is often called the "semi-expanded format" since the in-memory representation
309//! is still larger).
310//! Contact us if you need such a thing implemented.
311//!
312//! # Suspending and resuming execution via SerializableState
313//!
314//! When signing or verifying a large message, it can be advantageous to be able to suspend the operation
315//! to a cache and resume it later; for example if waiting for the message to stream over a slow network
316//! connection.
317//!
318//! This can bo accomplished for both the ML-DSA signer and verifier through the [`MuBuilder`] object.
319//!
320//! Suspending an in-progress sign operation:
321//!
322//! ```rust
323//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait};
324//! use bouncycastle_core::traits::{Signer, Suspendable};
325//!
326//! let msg_part1 = b"The quick brown fox";
327//! let msg_part2 = b" jumped over the lazy dog";
328//!
329//! let (pk, sk) = MLDSA65::keygen().unwrap();
330//!
331//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap();
332//! mb.do_update(msg_part1);
333//!
334//! // here, we'll suspend while "waiting" for the second part of the message
335//! let serialized_state = mb.suspend();
336//!
337//! // ...
338//! // do other things in the meantime
339//! // ...
340//!
341//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap();
342//! mb_resumed.do_update(msg_part2);
343//! let mu: [u8; 64] = mb_resumed.do_final();
344//!
345//! // Now we'll do the actual sign_mu operation
346//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap();
347//! ```
348//!
349//! Suspending an in-progress verify operation behaves exactly the same way:
350//!
351//! ```rust
352//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait};
353//! use bouncycastle_core::traits::{Signer, Suspendable};
354//! use bouncycastle_core::errors::SignatureError;
355//!
356//! let (pk, sk) = MLDSA65::keygen().unwrap();
357//!
358//! // first, let's generate a signature to verify
359//! let sig = MLDSA65::sign(&sk, b"The quick brown fox jumped over the lazy dog", None).unwrap();
360//!
361//! // Now we'll verify it with a suspension in the middle
362//! let msg_part1 = b"The quick brown fox";
363//! let msg_part2 = b" jumped over the lazy dog";
364//!
365//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap();
366//! mb.do_update(msg_part1);
367//!
368//! // here, we'll suspend while "waiting" for the second part of the message
369//! let serialized_state = mb.suspend();
370//!
371//! // ...
372//! // do other things in the meantime
373//! // ...
374//!
375//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap();
376//! mb_resumed.do_update(msg_part2);
377//! let mu: [u8; 64] = mb_resumed.do_final();
378//!
379//! // Now we'll do the actual verify_mu operation
380//! match MLDSA65::verify_mu(&pk, &mu, &sig) {
381//! Ok(()) => println!("Signature is valid!"),
382//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
383//! Err(e) => panic!("Something else went wrong: {:?}", e),
384//! }
385//! ```
386
387use crate::aux_functions::{
388 bitlen_eta, bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row,
389};
390use crate::low_memory_helpers::{
391 compute_ct0_component, compute_w_row, compute_w0cs2_component, compute_wp_approx_row,
392 compute_z_component, s_unpack,
393};
394use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait};
395use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait};
396use crate::{
397 MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey,
398 MLDSA87PublicKey,
399};
400use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError};
401use bouncycastle_core::key_material::KeyMaterial;
402use bouncycastle_core::traits::{
403 Algorithm, AlgorithmOID, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF,
404};
405use bouncycastle_rng::HashDRBG_SHA512;
406use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN};
407use core::marker::PhantomData;
408
409// imports needed just for docs
410#[allow(unused_imports)]
411use crate::hash_mldsa;
412#[allow(unused_imports)]
413use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait};
414#[allow(unused_imports)]
415use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner};
416use bouncycastle_utils::secret::Secret;
417/*** Constants ***/
418
419///
420pub const ML_DSA_44_NAME: &str = "ML-DSA-44";
421///
422pub const ML_DSA_65_NAME: &str = "ML-DSA-65";
423///
424pub const ML_DSA_87_NAME: &str = "ML-DSA-87";
425
426// From FIPS 204 Table 1 and Table 2
427
428// Constants that are the same for all parameter sets
429pub(crate) const N: usize = 256;
430pub(crate) const q: i32 = 8380417;
431pub(crate) const q_inv: i32 = 58728449; // q ^ (-1) mod 2 ^32
432pub(crate) const d: i32 = 13;
433/// Length of the \[u8] holding a ML-DSA signing random value.
434pub const MLDSA_RND_LEN: usize = 32;
435/// Length of the \[u8] holding a ML-DSA tr value (which is the SHAKE256 hash of the public key).
436pub const MLDSA_TR_LEN: usize = 64;
437/// Length of the \[u8] holding a ML-DSA mu value.
438pub const MLDSA_MU_LEN: usize = 64;
439/// Length of the \[u8] holding an private key seed.
440pub const MLDSA_SEED_LEN: usize = 32;
441pub(crate) const POLY_T0PACKED_LEN: usize = 416;
442pub(crate) const POLY_T1PACKED_LEN: usize = 320;
443
444/* ML-DSA-44 params */
445
446/// Length of the \[u8] holding a ML-DSA-44 public key.
447pub const MLDSA44_PK_LEN: usize = 1312;
448/// Length of the \[u8] holding a ML-DSA-44 private key, which in this implementation is just a 32-byte seed.
449pub const MLDSA44_SK_LEN: usize = MLDSA_SEED_LEN;
450/// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`]
451pub const MLDSA44_FULL_SK_LEN: usize = 2560;
452/// Length of the \[u8] holding a ML-DSA-44 signature value.
453pub const MLDSA44_SIG_LEN: usize = 2420;
454pub(crate) const MLDSA44_TAU: i32 = 39;
455pub(crate) const MLDSA44_LAMBDA: i32 = 128;
456pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17;
457pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88; // mutants note: because of the bitshifting, the "- 1" ends up not mattering
458pub(crate) const MLDSA44_k: usize = 4;
459pub(crate) const MLDSA44_l: usize = 4;
460pub(crate) const MLDSA44_ETA: usize = 2;
461pub(crate) const MLDSA44_BETA: i32 = 78;
462pub(crate) const MLDSA44_OMEGA: i32 = 80;
463
464// Useful derived values
465pub(crate) const MLDSA44_C_TILDE: usize = 32;
466pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576;
467pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192;
468pub(crate) const MLDSA44_S1_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_l; // 384 bytes
469pub(crate) const MLDSA44_S2_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_k; // 384 bytes
470pub(crate) const MLDSA44_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA44_k; // 768 bytes
471pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128 / 4;
472pub(crate) const MLDSA44_GAMMA1_MINUS_BETA: i32 = MLDSA44_GAMMA1 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
473pub(crate) const MLDSA44_GAMMA2_MINUS_BETA: i32 = MLDSA44_GAMMA2 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
474
475// Alg 32
476// 1: π β 1 + bitlen (πΎ1 β 1)
477pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (πΎ1 β 1) )
478
479/* ML-DSA-65 params */
480
481/// Length of the \[u8] holding a ML-DSA-65 public key.
482pub const MLDSA65_PK_LEN: usize = 1952;
483/// Length of the \[u8] holding a ML-DSA-65 private key, which in this implementation is just a 32-byte seed.
484pub const MLDSA65_SK_LEN: usize = MLDSA_SEED_LEN;
485/// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`]
486pub const MLDSA65_FULL_SK_LEN: usize = 4032;
487/// Length of the \[u8] holding a ML-DSA-65 signature value.
488pub const MLDSA65_SIG_LEN: usize = 3309;
489pub(crate) const MLDSA65_TAU: i32 = 49;
490pub(crate) const MLDSA65_LAMBDA: i32 = 192;
491pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19;
492pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering
493pub(crate) const MLDSA65_k: usize = 6;
494pub(crate) const MLDSA65_l: usize = 5;
495pub(crate) const MLDSA65_ETA: usize = 4;
496pub(crate) const MLDSA65_BETA: i32 = 196;
497pub(crate) const MLDSA65_OMEGA: i32 = 55;
498
499// Useful derived values
500pub(crate) const MLDSA65_C_TILDE: usize = 48;
501pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640;
502pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128;
503pub(crate) const MLDSA65_S1_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_l; // 640 bytes
504pub(crate) const MLDSA65_S2_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_k; // 768 bytes
505pub(crate) const MLDSA65_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA65_k; // 1152 bytes
506pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192 / 4;
507pub(crate) const MLDSA65_GAMMA1_MINUS_BETA: i32 = MLDSA65_GAMMA1 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
508pub(crate) const MLDSA65_GAMMA2_MINUS_BETA: i32 = MLDSA65_GAMMA2 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
509
510// Alg 32
511// 1: π β 1 + bitlen (πΎ1 β 1)
512pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640;
513
514/* ML-DSA-87 params */
515
516/// Length of the \[u8] holding a ML-DSA-87 public key.
517pub const MLDSA87_PK_LEN: usize = 2592;
518/// Length of the \[u8] holding a ML-DSA-87 private key, which in this implementation is just a 32-byte seed.
519pub const MLDSA87_SK_LEN: usize = MLDSA_SEED_LEN;
520/// The length of the FIPS representation of the private key, which can be produced by [`MLDSAPrivateKeyTrait::encode_full_sk`]
521pub const MLDSA87_FULL_SK_LEN: usize = 4896;
522/// Length of the \[u8] holding a ML-DSA-87 signature value.
523pub const MLDSA87_SIG_LEN: usize = 4627;
524pub(crate) const MLDSA87_TAU: i32 = 60;
525pub(crate) const MLDSA87_LAMBDA: i32 = 256;
526pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19;
527pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering
528pub(crate) const MLDSA87_k: usize = 8;
529pub(crate) const MLDSA87_l: usize = 7;
530pub(crate) const MLDSA87_ETA: usize = 2;
531pub(crate) const MLDSA87_BETA: i32 = 120;
532pub(crate) const MLDSA87_OMEGA: i32 = 75;
533
534// Useful derived values
535pub(crate) const MLDSA87_C_TILDE: usize = 64;
536pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640;
537pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128;
538pub(crate) const MLDSA87_S1_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_l; // 672 bytes
539pub(crate) const MLDSA87_S2_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_k; // 768 bytes
540pub(crate) const MLDSA87_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA87_k; // 1024 bytes
541pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256 / 4;
542pub(crate) const MLDSA87_GAMMA1_MINUS_BETA: i32 = MLDSA87_GAMMA1 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
543pub(crate) const MLDSA87_GAMMA2_MINUS_BETA: i32 = MLDSA87_GAMMA2 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but its sk seed is not known here, so can't test it here.
544
545// Alg 32
546// 1: π β 1 + bitlen (πΎ1 β 1)
547pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640;
548
549// Typedefs just to make the algorithms look more like the FIPS 204 sample code.
550pub(crate) type H = SHAKE256;
551pub(crate) type G = SHAKE128;
552
553/*** Pub Types ***/
554
555/// The ML-DSA-44 algorithm.
556pub type MLDSA44 = MLDSA<
557 MLDSA44_PK_LEN,
558 MLDSA44_SK_LEN,
559 MLDSA44_FULL_SK_LEN,
560 MLDSA44_SIG_LEN,
561 MLDSA44PublicKey,
562 MLDSA44PrivateKey,
563 MLDSA44_TAU,
564 MLDSA44_LAMBDA,
565 MLDSA44_GAMMA1,
566 MLDSA44_GAMMA2,
567 MLDSA44_k,
568 MLDSA44_l,
569 MLDSA44_ETA,
570 MLDSA44_BETA,
571 MLDSA44_OMEGA,
572 MLDSA44_C_TILDE,
573 MLDSA44_POLY_Z_PACKED_LEN,
574 MLDSA44_POLY_W1_PACKED_LEN,
575 MLDSA44_S1_PACKED_LEN,
576 MLDSA44_S2_PACKED_LEN,
577 MLDSA44_T1_PACKED_LEN,
578 MLDSA44_LAMBDA_over_4,
579 MLDSA44_GAMMA1_MINUS_BETA,
580 MLDSA44_GAMMA2_MINUS_BETA,
581 MLDSA44_GAMMA1_MASK_LEN,
582>;
583
584impl Algorithm for MLDSA44 {
585 const ALG_NAME: &'static str = ML_DSA_44_NAME;
586 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
587}
588/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 }
589impl AlgorithmOID for MLDSA44 {
590 const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17];
591 const OID_DER: &'static [u8] =
592 &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11];
593}
594
595/// The ML-DSA-65 algorithm.
596pub type MLDSA65 = MLDSA<
597 MLDSA65_PK_LEN,
598 MLDSA65_SK_LEN,
599 MLDSA65_FULL_SK_LEN,
600 MLDSA65_SIG_LEN,
601 MLDSA65PublicKey,
602 MLDSA65PrivateKey,
603 MLDSA65_TAU,
604 MLDSA65_LAMBDA,
605 MLDSA65_GAMMA1,
606 MLDSA65_GAMMA2,
607 MLDSA65_k,
608 MLDSA65_l,
609 MLDSA65_ETA,
610 MLDSA65_BETA,
611 MLDSA65_OMEGA,
612 MLDSA65_C_TILDE,
613 MLDSA65_POLY_Z_PACKED_LEN,
614 MLDSA65_POLY_W1_PACKED_LEN,
615 MLDSA65_S1_PACKED_LEN,
616 MLDSA65_S2_PACKED_LEN,
617 MLDSA65_T1_PACKED_LEN,
618 MLDSA65_LAMBDA_over_4,
619 MLDSA65_GAMMA1_MINUS_BETA,
620 MLDSA65_GAMMA2_MINUS_BETA,
621 MLDSA65_GAMMA1_MASK_LEN,
622>;
623
624impl Algorithm for MLDSA65 {
625 const ALG_NAME: &'static str = ML_DSA_65_NAME;
626 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
627}
628/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 }
629impl AlgorithmOID for MLDSA65 {
630 const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18];
631 const OID_DER: &'static [u8] =
632 &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12];
633}
634
635/// The ML-DSA-87 algorithm.
636pub type MLDSA87 = MLDSA<
637 MLDSA87_PK_LEN,
638 MLDSA87_SK_LEN,
639 MLDSA87_FULL_SK_LEN,
640 MLDSA87_SIG_LEN,
641 MLDSA87PublicKey,
642 MLDSA87PrivateKey,
643 MLDSA87_TAU,
644 MLDSA87_LAMBDA,
645 MLDSA87_GAMMA1,
646 MLDSA87_GAMMA2,
647 MLDSA87_k,
648 MLDSA87_l,
649 MLDSA87_ETA,
650 MLDSA87_BETA,
651 MLDSA87_OMEGA,
652 MLDSA87_C_TILDE,
653 MLDSA87_POLY_Z_PACKED_LEN,
654 MLDSA87_POLY_W1_PACKED_LEN,
655 MLDSA87_S1_PACKED_LEN,
656 MLDSA87_S2_PACKED_LEN,
657 MLDSA87_T1_PACKED_LEN,
658 MLDSA87_LAMBDA_over_4,
659 MLDSA87_GAMMA1_MINUS_BETA,
660 MLDSA87_GAMMA2_MINUS_BETA,
661 MLDSA87_GAMMA1_MASK_LEN,
662>;
663
664impl Algorithm for MLDSA87 {
665 const ALG_NAME: &'static str = ML_DSA_87_NAME;
666 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
667}
668/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 }
669impl AlgorithmOID for MLDSA87 {
670 const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19];
671 const OID_DER: &'static [u8] =
672 &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13];
673}
674
675/// The core internal implementation of the ML-DSA algorithm.
676/// This needs to be public for the compiler to be able to find it, but there shouldn't ever
677/// be a need to use this directly. Please use the named public types.
678pub struct MLDSA<
679 const PK_LEN: usize,
680 const SK_LEN: usize,
681 const FULL_SK_LEN: usize,
682 const SIG_LEN: usize,
683 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
684 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
685 SK: MLDSAPrivateKeyTrait<
686 k,
687 l,
688 S1_PACKED_LEN,
689 S2_PACKED_LEN,
690 T1_PACKED_LEN,
691 PK_LEN,
692 SK_LEN,
693 FULL_SK_LEN,
694 > + MLDSAPrivateKeyInternalTrait<
695 LAMBDA,
696 GAMMA2,
697 k,
698 l,
699 ETA,
700 S1_PACKED_LEN,
701 S2_PACKED_LEN,
702 PK_LEN,
703 SK_LEN,
704 >,
705 const TAU: i32,
706 const LAMBDA: i32,
707 const GAMMA1: i32,
708 const GAMMA2: i32,
709 const k: usize,
710 const l: usize,
711 const ETA: usize,
712 const BETA: i32,
713 const OMEGA: i32,
714 const C_TILDE: usize,
715 const POLY_VEC_H_PACKED_LEN: usize,
716 const POLY_W1_PACKED_LEN: usize,
717 const S1_PACKED_LEN: usize,
718 const S2_PACKED_LEN: usize,
719 const T1_PACKED_LEN: usize,
720 const LAMBDA_over_4: usize,
721 const GAMMA1_MINUS_BETA: i32,
722 const GAMMA2_MINUS_BETA: i32,
723 const GAMMA1_MASK_LEN: usize,
724> {
725 _phantom: PhantomData<(PK, SK)>,
726
727 /// used for streaming the message for both signing and verifying
728 mu_builder: MuBuilder,
729
730 signer_rnd: Option<[u8; MLDSA_RND_LEN]>,
731
732 /// only used in streaming sign operations
733 sk: Option<SK>,
734
735 /// only used in streaming sign operations instead of sk
736 seed: Option<KeyMaterial<32>>,
737
738 /// only used in streaming verify operations
739 pk: Option<PK>,
740}
741
742impl<
743 const PK_LEN: usize,
744 const SK_LEN: usize,
745 const FULL_SK_LEN: usize,
746 const SIG_LEN: usize,
747 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
748 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
749 SK: MLDSAPrivateKeyTrait<
750 k,
751 l,
752 S1_PACKED_LEN,
753 S2_PACKED_LEN,
754 T1_PACKED_LEN,
755 PK_LEN,
756 SK_LEN,
757 FULL_SK_LEN,
758 > + MLDSAPrivateKeyInternalTrait<
759 LAMBDA,
760 GAMMA2,
761 k,
762 l,
763 ETA,
764 S1_PACKED_LEN,
765 S2_PACKED_LEN,
766 PK_LEN,
767 SK_LEN,
768 >,
769 const TAU: i32,
770 const LAMBDA: i32,
771 const GAMMA1: i32,
772 const GAMMA2: i32,
773 const k: usize,
774 const l: usize,
775 const ETA: usize,
776 const BETA: i32,
777 const OMEGA: i32,
778 const C_TILDE: usize,
779 const POLY_Z_PACKED_LEN: usize,
780 const POLY_W1_PACKED_LEN: usize,
781 const S1_PACKED_LEN: usize,
782 const S2_PACKED_LEN: usize,
783 const T1_PACKED_LEN: usize,
784 const LAMBDA_over_4: usize,
785 const GAMMA1_MINUS_BETA: i32,
786 const GAMMA2_MINUS_BETA: i32,
787 const GAMMA1_MASK_LEN: usize,
788>
789 MLDSA<
790 PK_LEN,
791 SK_LEN,
792 FULL_SK_LEN,
793 SIG_LEN,
794 PK,
795 SK,
796 TAU,
797 LAMBDA,
798 GAMMA1,
799 GAMMA2,
800 k,
801 l,
802 ETA,
803 BETA,
804 OMEGA,
805 C_TILDE,
806 POLY_Z_PACKED_LEN,
807 POLY_W1_PACKED_LEN,
808 S1_PACKED_LEN,
809 S2_PACKED_LEN,
810 T1_PACKED_LEN,
811 LAMBDA_over_4,
812 GAMMA1_MINUS_BETA,
813 GAMMA2_MINUS_BETA,
814 GAMMA1_MASK_LEN,
815 >
816{
817 /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds.
818 ///
819 /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one
820 /// specifically takes a 32-byte [`KeyMaterial256`] and checks that it has [`KeyType::Seed`] and
821 /// the appropriate [`SecurityStrength`] for the requested ML-DSA parameter set.
822 ///
823 /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it into a
824 /// correctly-sized [`KeyMaterial256`] using [`KeyMaterialTrait::truncate`].
825 pub(crate) fn keygen_internal(seed: &KeyMaterial256) -> Result<(PK, SK), SignatureError> {
826 let sk = SK::from_keymaterial(seed)?;
827 let pk = sk.derive_pk();
828 let pk = PK::new(pk.rho, pk.t1_packed); // type-loundering to satisfy the checker
829 Ok((pk, sk))
830 }
831}
832
833impl<
834 const PK_LEN: usize,
835 const SK_LEN: usize,
836 const FULL_SK_LEN: usize,
837 const SIG_LEN: usize,
838 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
839 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
840 SK: MLDSAPrivateKeyTrait<
841 k,
842 l,
843 S1_PACKED_LEN,
844 S2_PACKED_LEN,
845 T1_PACKED_LEN,
846 PK_LEN,
847 SK_LEN,
848 FULL_SK_LEN,
849 > + MLDSAPrivateKeyInternalTrait<
850 LAMBDA,
851 GAMMA2,
852 k,
853 l,
854 eta,
855 S1_PACKED_LEN,
856 S2_PACKED_LEN,
857 PK_LEN,
858 SK_LEN,
859 >,
860 const TAU: i32,
861 const LAMBDA: i32,
862 const GAMMA1: i32,
863 const GAMMA2: i32,
864 const k: usize,
865 const l: usize,
866 const eta: usize,
867 const BETA: i32,
868 const OMEGA: i32,
869 const C_TILDE: usize,
870 const POLY_Z_PACKED_LEN: usize,
871 const POLY_W1_PACKED_LEN: usize,
872 const S1_PACKED_LEN: usize,
873 const S2_PACKED_LEN: usize,
874 const T1_PACKED_LEN: usize,
875 const LAMBDA_over_4: usize,
876 const GAMMA1_MINUS_BETA: i32,
877 const GAMMA2_MINUS_BETA: i32,
878 const GAMMA1_MASK_LEN: usize,
879>
880 MLDSATrait<
881 PK_LEN,
882 SK_LEN,
883 FULL_SK_LEN,
884 SIG_LEN,
885 PK,
886 SK,
887 LAMBDA,
888 GAMMA2,
889 k,
890 l,
891 S1_PACKED_LEN,
892 S2_PACKED_LEN,
893 T1_PACKED_LEN,
894 eta,
895 >
896 for MLDSA<
897 PK_LEN,
898 SK_LEN,
899 FULL_SK_LEN,
900 SIG_LEN,
901 PK,
902 SK,
903 TAU,
904 LAMBDA,
905 GAMMA1,
906 GAMMA2,
907 k,
908 l,
909 eta,
910 BETA,
911 OMEGA,
912 C_TILDE,
913 POLY_Z_PACKED_LEN,
914 POLY_W1_PACKED_LEN,
915 S1_PACKED_LEN,
916 S2_PACKED_LEN,
917 T1_PACKED_LEN,
918 LAMBDA_over_4,
919 GAMMA1_MINUS_BETA,
920 GAMMA2_MINUS_BETA,
921 GAMMA1_MASK_LEN,
922 >
923{
924 /*** Key Generation and PK / SK consistency checks ***/
925
926 /// Imports a secret key from a seed.
927 fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> {
928 Self::keygen_internal(seed)
929 }
930 /// Imports a secret key from both a seed and an encoded_sk.
931 ///
932 /// This is a convenience function to expand the key from seed and compare it against
933 /// the provided `encoded_sk` using a constant-time equality check.
934 /// If everything checks out, the secret key is returned fully populated with pk and seed.
935 /// If the provided key and derived key don't match, an error is returned.
936 fn keygen_from_seed_and_encoded(
937 seed: &KeyMaterial<32>,
938 encoded_sk: &[u8; SK_LEN],
939 ) -> Result<(PK, SK), SignatureError> {
940 let (pk, sk) = Self::keygen_internal(seed)?;
941
942 let sk_from_bytes = SK::sk_decode(encoded_sk);
943
944 // MLDSAPrivateKey impls PartialEq with a constant-time equality check.
945 if sk != sk_from_bytes {
946 return Err(SignatureError::KeyGenError("Encoded key does not match generated key"));
947 }
948
949 Ok((pk, sk))
950 }
951 /// Given a public key and a secret key, check that the public key matches the secret key.
952 /// This is a sanity check that the public key was generated correctly from the secret key.
953 ///
954 /// At the current time, this is only possible if `sk` either contains a public key (in which case
955 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
956 /// (in which case a keygen_from_seed is run and then the pk's compared).
957 ///
958 /// Returns either `()` or [`SignatureError::ConsistencyCheckFailed`].
959 fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), SignatureError> {
960 // This is maybe a computationally heavy way to compare them, but it works
961 let derived_pk = sk.derive_pk();
962 if derived_pk.compute_tr() == pk.compute_tr() {
963 Ok(())
964 } else {
965 Err(SignatureError::ConsistencyCheckFailed())
966 }
967 }
968 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
969 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
970 ///
971 /// This function, together with [`MLDSATrait::sign_mu`] perform a complete ML-DSA signature which is indistinguishable
972 /// from one produced by the one-shot sign APIs.
973 ///
974 /// The utility of this function is exactly as described
975 /// on Line 6 of Algorithm 7 of FIPS 204:
976 ///
977 /// message representative that may optionally be computed in a different cryptographic module
978 ///
979 /// The utility is when an extremely large message needs to be signed, where the message exists on one
980 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
981 /// causes operational concerns (this is common for example with network HSMs or sending large messages
982 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
983 /// contents of the message are sensitive and the signer does not want to transmit the message itself
984 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
985 ///
986 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
987 /// can be used with many hardware crypto modules.
988 ///
989 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
990 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
991 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
992 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
993 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
994 /// ML-DSA verifier.
995 ///
996 /// This function requires the public key hash `tr`, which can be computed from the public key
997 /// using [`MLDSAPublicKeyTrait::compute_tr`].
998 ///
999 /// For a streaming version of this, see [`MuBuilder`].
1000 fn compute_mu_from_tr(
1001 tr: &[u8; 64],
1002 msg: &[u8],
1003 ctx: Option<&[u8]>,
1004 ) -> Result<[u8; 64], SignatureError> {
1005 MuBuilder::compute_mu(tr, msg, ctx)
1006 }
1007 /// Same as [`MLDSA::compute_mu_from_tr`], but extracts tr from the public key.
1008 fn compute_mu_from_pk(
1009 pk: &PK,
1010 msg: &[u8],
1011 ctx: Option<&[u8]>,
1012 ) -> Result<[u8; 64], SignatureError> {
1013 MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)
1014 }
1015 /// Same as [`MLDSA::compute_mu_from_tr`], but extracts tr from the private key.
1016 fn compute_mu_from_sk(
1017 sk: &SK,
1018 msg: &[u8],
1019 ctx: Option<&[u8]>,
1020 ) -> Result<[u8; 64], SignatureError> {
1021 MuBuilder::compute_mu(&sk.tr(), msg, ctx)
1022 }
1023 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1024 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1025 /// FIPS 204 itself, as well as subsequent FAQ documents.
1026 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1027 fn sign_mu(sk: &SK, mu: &[u8; 64]) -> Result<[u8; SIG_LEN], SignatureError> {
1028 let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN];
1029 Self::sign_mu_out(sk, mu, &mut out)?;
1030 Ok(out)
1031 }
1032 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1033 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1034 /// FIPS 204 itself, as well as subsequent FAQ documents.
1035 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1036 ///
1037 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1038 fn sign_mu_out(
1039 sk: &SK,
1040 mu: &[u8; 64],
1041 output: &mut [u8; SIG_LEN],
1042 ) -> Result<usize, SignatureError> {
1043 output.fill(0);
1044
1045 let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
1046 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
1047
1048 Self::sign_mu_deterministic_out(sk, mu, rnd, output)
1049 }
1050
1051 fn sign_mu_deterministic(
1052 sk: &SK,
1053 mu: &[u8; 64],
1054 rnd: [u8; 32],
1055 ) -> Result<[u8; SIG_LEN], SignatureError> {
1056 let mut out = [0u8; SIG_LEN];
1057 let bytes_written = Self::sign_mu_deterministic_out(sk, mu, rnd, &mut out)?;
1058 debug_assert_eq!(bytes_written, SIG_LEN);
1059 Ok(out)
1060 }
1061 /// This function is a mash-up of keyGen (Algorithm 6) and sign (Algorithm 7),
1062 /// with a special emphasis on deriving values only as they are needed, which in particular
1063 /// means that matrices and vectors are processed row or component-wise.
1064 fn sign_mu_deterministic_out(
1065 sk: &SK,
1066 mu: &[u8; 64],
1067 rnd: [u8; 32],
1068 output: &mut [u8; SIG_LEN],
1069 ) -> Result<usize, SignatureError> {
1070 output.fill(0);
1071
1072 // This function is a mash-up of keyGen (Algorithm 6) and sign (Algorithm 7),
1073 // with a special emphasis on deriving values only as they are needed, which in particular
1074 // means that matrices and vectors are processed row or component-wise.
1075
1076 // This has been kept as clean as possible for correspondence with the FIPS,
1077 // but things have been moved around so that unnamed scopes can be used to limit how many
1078 // stack variables are alive at the same time.
1079
1080 // 1: (π, πΎ, π‘π, π¬1, π¬2, π0) β skDecode(π π)
1081 // to avoid having all of it in memory at the same time,
1082 // components are derived as they are needed.
1083
1084 // [Optimization Note]:
1085 // s1 and s2 are normally part of the stored private key.
1086 // They are used many times through this function,
1087 // so they are being computed here and kept in the compressed encoding specified in
1088 // FIPS 204 Alg 17.
1089 // They are uncompresso as-needed, and only one polynomial at a time.
1090 // Storing these in memory can be avoided, but then all the sites where they are used
1091 // will require calls to sk.compute_s1_row() and sk.compute_s2_row(), which are fairly expensive.
1092 let s1_packed: Secret<[u8; S1_PACKED_LEN]> = sk.compute_s1_packed();
1093 let s2_packed: Secret<[u8; S2_PACKED_LEN]> = sk.compute_s2_packed();
1094
1095 // 6: π β H(BytesToBits(π‘π)||π β², 64)
1096 // skip: mu has already been provided
1097
1098 // Alg 7; 7: πβ³ β H(πΎ||πππ||π, 64)
1099 let rho_p_p: [u8; 64] = {
1100 let mut h = H::new();
1101 h.absorb(sk.K()).expect("absorb before squeeze is infallible");
1102 h.absorb(&rnd).expect("absorb before squeeze is infallible");
1103 h.absorb(mu).expect("absorb before squeeze is infallible");
1104 let mut rho_p_p = [0u8; 64];
1105 h.squeeze_out(&mut rho_p_p);
1106
1107 rho_p_p
1108 };
1109
1110 // 8: π
β 0
1111 // β· initialize counter π
1112 let mut kappa: u16 = 0;
1113
1114 let z_offset = LAMBDA_over_4;
1115 let hint_offset = LAMBDA_over_4 + l * POLY_Z_PACKED_LEN;
1116
1117 loop {
1118 // FIPS 204 s. 6.2 allows:
1119 // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value."
1120 // mutants note: there is no test for this because we don't have access to a KAT that will exceed this limit.
1121 if kappa > 1000 * k as u16 {
1122 return Err(SignatureError::GenericError(
1123 "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.",
1124 ));
1125 }
1126
1127 // 11-15: derive c_tilde without materializing y_hat or w as full vectors.
1128 let sig_val_c_tilde = {
1129 // scope for hash
1130 let mut hash = H::new();
1131 hash.absorb(mu).expect("absorb before squeeze is infallible");
1132 for row in 0..k {
1133 let mut w = compute_w_row::<l, GAMMA1, GAMMA1_MASK_LEN>(
1134 &sk.rho(),
1135 &rho_p_p,
1136 kappa,
1137 row,
1138 );
1139 w.high_bits::<GAMMA2>();
1140 hash.absorb(&w.w1_encode::<POLY_W1_PACKED_LEN>())
1141 .expect("absorb before squeeze is infallible");
1142 }
1143 let mut sig_val_c_tilde = [0u8; LAMBDA_over_4];
1144 hash.squeeze_out(&mut sig_val_c_tilde);
1145 sig_val_c_tilde
1146 };
1147 // 16: π β π
π β SampleInBall(c_tilde)
1148 // 17: π_hat β NTT(π)
1149 // optimization note: c_hat is used basically until the end, it can't really be scoped
1150 let mut c_hat = sample_in_ball::<LAMBDA_over_4, TAU>(&sig_val_c_tilde);
1151 c_hat.ntt();
1152
1153 output.fill(0);
1154 output[..LAMBDA_over_4].copy_from_slice(&sig_val_c_tilde);
1155
1156 let (z_chunks, z_remainder) = output[z_offset..z_offset + l * POLY_Z_PACKED_LEN]
1157 .as_chunks_mut::<POLY_Z_PACKED_LEN>();
1158 debug_assert_eq!(z_chunks.len(), l);
1159 debug_assert_eq!(z_remainder.len(), 0);
1160
1161 // 18-23 (z path): compute and encode each z polynomial directly into the caller buffer.
1162 let mut rejected = false;
1163 for col in 0..l {
1164 let z = match compute_z_component::<GAMMA1, GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA>(
1165 // [Optimization Note]:
1166 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
1167 // weirdly, in perf testing, this actually caused memory usage to go by a small amount;
1168 // maybe because re-computing the intermediates adds more to the widest point of the alg?
1169 // &sk.compute_s1_row(col),
1170 &s_unpack::<eta, S1_PACKED_LEN>(&s1_packed, col),
1171 &rho_p_p,
1172 &c_hat,
1173 kappa,
1174 col,
1175 )? {
1176 Some(z) => z,
1177 None => {
1178 rejected = true;
1179 break;
1180 }
1181 };
1182
1183 bitpack_gamma1::<POLY_Z_PACKED_LEN, GAMMA1>(&z, &mut z_chunks[col]);
1184 }
1185
1186 if rejected {
1187 // mutants note: we don't have access to a test vector that exercises this
1188 kappa += l as u16;
1189 continue;
1190 }
1191
1192 // 19-28 (hint path): recompute rows as needed and write the packed hint directly.
1193 let mut hint_count = 0usize;
1194 for row in 0..k {
1195 let mut w =
1196 compute_w_row::<l, GAMMA1, GAMMA1_MASK_LEN>(&sk.rho(), &rho_p_p, kappa, row);
1197 let mut tmp = match compute_w0cs2_component::<GAMMA2, GAMMA2_MINUS_BETA>(
1198 // [Optimization Note]:
1199 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
1200 // &sk.compute_s2_row(row),
1201 &s_unpack::<eta, S2_PACKED_LEN>(&s2_packed, row),
1202 &w,
1203 &c_hat,
1204 ) {
1205 Some(tmp) => tmp,
1206 None => {
1207 rejected = true;
1208 break;
1209 }
1210 };
1211
1212 let ct0 = match compute_ct0_component::<GAMMA2>(
1213 // [Optimization Note]:
1214 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
1215 // &sk.compute_t0_row(row), &c_hat) {
1216 &sk.compute_t0_row(row, &s1_packed, &s2_packed),
1217 &c_hat,
1218 ) {
1219 Some(ct0) => ct0,
1220 None => {
1221 rejected = true;
1222 break;
1223 }
1224 };
1225
1226 tmp.add_ntt(&ct0);
1227 tmp.conditional_add_q();
1228
1229 w.high_bits::<GAMMA2>();
1230 let (hint_row, weight) = tmp.make_hint_row::<GAMMA2>(&w);
1231 let next_hint_count = hint_count + weight as usize;
1232
1233 // mutants note: don't have a test vector that exercises this condition,
1234 // not even in bc-test-data
1235 if next_hint_count > OMEGA as usize {
1236 rejected = true;
1237 break;
1238 }
1239
1240 for idx in 0..N {
1241 if hint_row[idx] != 0 {
1242 output[hint_offset + hint_count] = idx as u8;
1243 hint_count += 1;
1244 }
1245 }
1246 debug_assert_eq!(hint_count, next_hint_count);
1247 output[hint_offset + OMEGA as usize + row] = hint_count as u8;
1248 }
1249
1250 if rejected {
1251 kappa += l as u16;
1252 continue;
1253 }
1254
1255 break;
1256 }
1257
1258 Ok(SIG_LEN)
1259 }
1260
1261 fn sign_mu_deterministic_from_seed(
1262 seed: &KeyMaterial<32>,
1263 mu: &[u8; 64],
1264 rnd: [u8; 32],
1265 ) -> Result<[u8; SIG_LEN], SignatureError> {
1266 let mut out = [0u8; SIG_LEN];
1267 SK::from_keymaterial(&seed)?;
1268 Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, &mut out)?;
1269 Ok(out)
1270 }
1271
1272 fn sign_mu_deterministic_from_seed_out(
1273 seed: &KeyMaterial<32>,
1274 mu: &[u8; 64],
1275 rnd: [u8; 32],
1276 output: &mut [u8; SIG_LEN],
1277 ) -> Result<usize, SignatureError> {
1278 output.fill(0);
1279
1280 SK::from_keymaterial(&seed)?;
1281 Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, output)
1282 }
1283
1284 /// To be used for deterministic signing in conjunction with the
1285 /// [`MLDSA44::sign_init`], [`MLDSA44::sign_update`], and [`MLDSA44::sign_final`] flow.
1286 /// Can be set anywhere after [`MLDSA44::sign_init`] and before [`MLDSA44::sign_final`]
1287 fn set_signer_rnd(&mut self, rnd: [u8; 32]) {
1288 self.signer_rnd = Some(rnd);
1289 }
1290
1291 /// Alternative initialization of the streaming signer where the user has their private key
1292 /// as a seed and they want to delay its expansion as late as possible for memory-usage reasons.
1293 fn sign_init_from_seed(
1294 seed: &KeyMaterial<32>,
1295 ctx: Option<&[u8]>,
1296 ) -> Result<Self, SignatureError> {
1297 let (_pk, sk) = Self::keygen_from_seed(seed)?;
1298 Ok(Self {
1299 _phantom: PhantomData,
1300 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1301 signer_rnd: None,
1302 sk: None,
1303 seed: Some(seed.clone()),
1304 pk: None,
1305 })
1306 }
1307
1308 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1309 /// Internal function to verify a signature π for a formatted message πβ² .
1310 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1311 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1312 fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError> {
1313 // 1: (π, π1) β pkDecode(ππ)
1314 // Already done -- the pk struct is already decoded
1315
1316 // 5: π β ExpandA(π)
1317 // β· π is generated and stored in NTT representation as π
1318 // This is done one row / polynomial at a time to reduce peak memory usage so that
1319 // the entirety of A_hat is never in memory at the same time.
1320
1321 // 6: π‘π β H(ππ, 64)
1322 // 7: π β (H(BytesToBits(π‘π)||π β², 64))
1323 // β· message representative that may optionally be
1324 // computed in a different cryptographic module
1325 // skip because this function is being handed mu
1326
1327 // 8: π β π
π β SampleInBall(c_tilde)
1328 let c = sample_in_ball::<LAMBDA_over_4, TAU>(unpack_c_tilde(sig));
1329
1330 // 12: π_tilde_p β H(π||w1Encode(π°1'), π/4)
1331 // β· hash it; this should match π_tilde
1332 let mut hash = H::new();
1333 hash.absorb(mu).expect("absorb before squeeze is infallible");
1334
1335 for row in 0..k {
1336 let mut wp_approx = match {
1337 // 9: π°β²_approx β NTTβ1(π_hat β NTT(π³) β NTT(π) β NTT(π1 β
2^π))
1338 compute_wp_approx_row::<
1339 GAMMA1,
1340 GAMMA1_MINUS_BETA,
1341 l,
1342 POLY_Z_PACKED_LEN,
1343 LAMBDA_over_4,
1344 SIG_LEN,
1345 >(pk.rho(), sig, &pk.unpack_t1_row(row), &c, row)
1346 } {
1347 Ok(wp_approx) => wp_approx,
1348 // means the norm check on z failed
1349 Err(_) => return Err(SignatureError::SignatureVerificationFailed),
1350 };
1351
1352 let h_i = match unpack_h_row::<
1353 GAMMA1,
1354 k,
1355 l,
1356 OMEGA,
1357 LAMBDA_over_4,
1358 POLY_Z_PACKED_LEN,
1359 SIG_LEN,
1360 >(row, &sig)
1361 {
1362 Some(h_i) => h_i,
1363 // means there were more than OMEGA bits set in the hint
1364 None => return Err(SignatureError::SignatureVerificationFailed),
1365 };
1366
1367 // 10: π°1β² β UseHint(π‘, π°'_approx)
1368 // β· reconstruction of signerβs commitment
1369 wp_approx.use_hint::<GAMMA2>(&h_i);
1370 hash.absorb(&wp_approx.w1_encode::<POLY_W1_PACKED_LEN>())
1371 .expect("absorb before squeeze is infallible");
1372 }
1373
1374 let mut c_tilde_p = [0u8; LAMBDA_over_4];
1375 hash.squeeze_out(&mut c_tilde_p);
1376
1377 // Verification is also done in constant time
1378 // 13 (second half): return [[ ||π³||β < πΎ1 β π½]] and [[π Μ = πβ² ]]
1379 // note: the first half of this check (the norm check) is buried in unpack_z_row(),
1380 // which is called from compute_wp_approx_row()
1381 if bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::<LAMBDA_over_4>(sig), &c_tilde_p) {
1382 Ok(())
1383 } else {
1384 Err(SignatureError::SignatureVerificationFailed)
1385 }
1386 }
1387}
1388
1389/// Trait for all three of the ML-DSA algorithm variants.
1390pub trait MLDSATrait<
1391 const PK_LEN: usize,
1392 const SK_LEN: usize,
1393 const FULL_SK_LEN: usize,
1394 const SIG_LEN: usize,
1395 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
1396 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
1397 SK: MLDSAPrivateKeyTrait<
1398 k,
1399 l,
1400 S1_PACKED_LEN,
1401 S2_PACKED_LEN,
1402 T1_PACKED_LEN,
1403 PK_LEN,
1404 SK_LEN,
1405 FULL_SK_LEN,
1406 > + MLDSAPrivateKeyInternalTrait<
1407 LAMBDA,
1408 GAMMA2,
1409 k,
1410 l,
1411 ETA,
1412 S1_PACKED_LEN,
1413 S2_PACKED_LEN,
1414 PK_LEN,
1415 SK_LEN,
1416 >,
1417 const LAMBDA: i32,
1418 const GAMMA2: i32,
1419 const k: usize,
1420 const l: usize,
1421 const S1_PACKED_LEN: usize,
1422 const S2_PACKED_LEN: usize,
1423 const T1_PACKED_LEN: usize,
1424 const ETA: usize,
1425>: Sized
1426{
1427 /// Runs a key generation using the library's default RNG, seeded from the OS.
1428 /// In environments where the default OS based RNG is not available, use instead [`MLDSA::keygen_from_rng`]
1429 /// and explicitly provide a [`RNG`] implementation, or use [`MLDSATrait::keygen_from_seed`] and provide the
1430 /// private key seed directly.
1431 fn keygen() -> Result<(PK, SK), SignatureError> {
1432 let mut os_rng = HashDRBG_SHA512::new_from_os();
1433 Self::keygen_from_rng(&mut os_rng)
1434 }
1435 /// Run a keygen using the provided RNG implementation.
1436 // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG.
1437 fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> {
1438 // Source the seed from the provided RNG
1439 if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
1440 return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?;
1441 }
1442 let mut seed = KeyMaterial::<32>::new();
1443 rng.fill_keymaterial_out(&mut seed)?;
1444 Self::keygen_from_seed(&seed)
1445 }
1446 /// Imports a secret key from a seed.
1447 fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>;
1448 /// Imports a secret key from both a seed and an encoded_sk.
1449 ///
1450 /// This is a convenience function to expand the key from seed and compare it against
1451 /// the provided `encoded_sk` using a constant-time equality check.
1452 /// If everything checks out, the secret key is returned fully populated with pk and seed.
1453 /// If the provided key and derived key don't match, an error is returned.
1454 fn keygen_from_seed_and_encoded(
1455 seed: &KeyMaterial<32>,
1456 encoded_sk: &[u8; SK_LEN],
1457 ) -> Result<(PK, SK), SignatureError>;
1458 /// Given a public key and a secret key, check that the public key matches the secret key.
1459 /// This is a sanity check that the public key was generated correctly from the secret key.
1460 ///
1461 /// At the current time, this is only possible if `sk` either contains a public key (in which case
1462 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
1463 /// (in which case a keygen_from_seed is run and then the pk's compared).
1464 ///
1465 /// Returns either `()` or [`SignatureError::ConsistencyCheckFailed`].
1466 fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), SignatureError>;
1467 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
1468 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
1469 ///
1470 /// This function, together with [`MLDSATrait::sign_mu`] perform a complete ML-DSA signature which is indistinguishable
1471 /// from one produced by the one-shot sign APIs.
1472 ///
1473 /// The utility of this function is exactly as described
1474 /// on Line 6 of Algorithm 7 of FIPS 204:
1475 ///
1476 /// message representative that may optionally be computed in a different cryptographic module
1477 ///
1478 /// The utility is when an extremely large message needs to be signed, where the message exists on one
1479 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
1480 /// causes operational concerns (this is common for example with network HSMs or sending large messages
1481 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
1482 /// contents of the message are sensitive and the signer does not want to transmit the message itself
1483 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
1484 ///
1485 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
1486 /// can be used with many hardware crypto modules.
1487 ///
1488 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
1489 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
1490 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
1491 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
1492 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
1493 /// ML-DSA verifier.
1494 ///
1495 /// This function requires the public key hash `tr`, which can be computed from the public key
1496 /// using [`MLDSAPublicKeyTrait::compute_tr`].
1497 ///
1498 /// For a streaming version of this, see [`MuBuilder`].
1499 fn compute_mu_from_tr(
1500 tr: &[u8; 64],
1501 msg: &[u8],
1502 ctx: Option<&[u8]>,
1503 ) -> Result<[u8; 64], SignatureError>;
1504 /// Same as [`MLDSATrait::compute_mu_from_tr`], but extracts tr from the public key.
1505 fn compute_mu_from_pk(
1506 pk: &PK,
1507 msg: &[u8],
1508 ctx: Option<&[u8]>,
1509 ) -> Result<[u8; 64], SignatureError>;
1510 /// Same as [`MLDSATrait::compute_mu_from_tr`], but extracts tr from the private key.
1511 fn compute_mu_from_sk(
1512 sk: &SK,
1513 msg: &[u8],
1514 ctx: Option<&[u8]>,
1515 ) -> Result<[u8; 64], SignatureError>;
1516 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1517 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1518 /// FIPS 204 itself, as well as subsequent FAQ documents.
1519 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1520 fn sign_mu(sk: &SK, mu: &[u8; 64]) -> Result<[u8; SIG_LEN], SignatureError>;
1521 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1522 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1523 /// FIPS 204 itself, as well as subsequent FAQ documents.
1524 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1525 ///
1526 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1527 fn sign_mu_out(
1528 sk: &SK,
1529 mu: &[u8; 64],
1530 output: &mut [u8; SIG_LEN],
1531 ) -> Result<usize, SignatureError>;
1532 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1533 /// (modified to take an externally-computed mu instead of M')
1534 ///
1535 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1536 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1537 /// FIPS 204 itself, as well as subsequent FAQ documents.
1538 ///
1539 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1540 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1541 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1542 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1543 /// of taking it by reference.
1544 ///
1545 /// Security note about deterministic mode:
1546 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1547 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1548 /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature.
1549 /// If not, some privacy properties may be lost; for example it becomes easy to tell if a signer
1550 /// has signed the same message twice or two different messagase, or to tell if the same message
1551 /// has been signed by the same signer twice or two different signers.
1552 fn sign_mu_deterministic(
1553 sk: &SK,
1554 mu: &[u8; 64],
1555 rnd: [u8; 32],
1556 ) -> Result<[u8; SIG_LEN], SignatureError>;
1557 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1558 /// (modified to take an externally-computed mu instead of M')
1559 ///
1560 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1561 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1562 /// FIPS 204 itself, as well as subsequent FAQ documents.
1563 /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG.
1564 ///
1565 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1566 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1567 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1568 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1569 /// of taking it by reference.
1570 ///
1571 /// Security note about deterministic mode:
1572 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1573 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1574 /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature.
1575 /// If not, some privacy properties may be lost; for example it becomes easy to tell if a signer
1576 /// has signed the same message twice or two different messagase, or to tell if the same message
1577 /// has been signed by the same signer twice or two different signers.
1578 ///
1579 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1580 fn sign_mu_deterministic_out(
1581 sk: &SK,
1582 mu: &[u8; 64],
1583 rnd: [u8; 32],
1584 output: &mut [u8; SIG_LEN],
1585 ) -> Result<usize, SignatureError>;
1586 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1587 /// memory usage by never having the full secret key in memory at the same time,
1588 /// and by deriving intermediate values piece-wise as needed.
1589 fn sign_mu_deterministic_from_seed(
1590 seed: &KeyMaterial<32>,
1591 mu: &[u8; 64],
1592 rnd: [u8; 32],
1593 ) -> Result<[u8; SIG_LEN], SignatureError>;
1594 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1595 /// memory usage by never having the full secret key in memory at the same time,
1596 /// and by deriving intermediate values piece-wise as needed.
1597 fn sign_mu_deterministic_from_seed_out(
1598 seed: &KeyMaterial<32>,
1599 mu: &[u8; 64],
1600 rnd: [u8; 32],
1601 output: &mut [u8; SIG_LEN],
1602 ) -> Result<usize, SignatureError>;
1603 /// To be used for deterministic signing in conjunction with the [`MLDSA44::sign_init`], [`MLDSA44::sign_update`], and [`MLDSA44::sign_final`] flow.
1604 /// Can be set anywhere after [`MLDSA44::sign_init`] and before [`MLDSA44::sign_final`]
1605 fn set_signer_rnd(&mut self, rnd: [u8; 32]);
1606 /// An alternate way to start the streaming signing mode by providing a private key seed instead of an expanded private key
1607 fn sign_init_from_seed(
1608 seed: &KeyMaterial<32>,
1609 ctx: Option<&[u8]>,
1610 ) -> Result<Self, SignatureError>;
1611 /// Performs an ML-DSA signature verification using the provided external message representative `mu`.
1612 /// This implements FIPS 204 Algorithm 8 with line 7 removed; a modification that is allowed by both
1613 /// FIPS 204 itself, as well as subsequent FAQ documents.
1614 fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError>;
1615}
1616
1617impl<
1618 const PK_LEN: usize,
1619 const SK_LEN: usize,
1620 const FULL_SK_LEN: usize,
1621 const SIG_LEN: usize,
1622 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
1623 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
1624 SK: MLDSAPrivateKeyTrait<
1625 k,
1626 l,
1627 S1_PACKED_LEN,
1628 S2_PACKED_LEN,
1629 T1_PACKED_LEN,
1630 PK_LEN,
1631 SK_LEN,
1632 FULL_SK_LEN,
1633 > + MLDSAPrivateKeyInternalTrait<
1634 LAMBDA,
1635 GAMMA2,
1636 k,
1637 l,
1638 ETA,
1639 S1_PACKED_LEN,
1640 S2_PACKED_LEN,
1641 PK_LEN,
1642 SK_LEN,
1643 >,
1644 const TAU: i32,
1645 const LAMBDA: i32,
1646 const GAMMA1: i32,
1647 const GAMMA2: i32,
1648 const k: usize,
1649 const l: usize,
1650 const ETA: usize,
1651 const BETA: i32,
1652 const OMEGA: i32,
1653 const C_TILDE: usize,
1654 const POLY_Z_PACKED_LEN: usize,
1655 const POLY_W1_PACKED_LEN: usize,
1656 const S1_PACKED_LEN: usize,
1657 const S2_PACKED_LEN: usize,
1658 const T1_PACKED_LEN: usize,
1659 const LAMBDA_over_4: usize,
1660 const GAMMA1_MINUS_BETA: i32,
1661 const GAMMA2_MINUS_BETA: i32,
1662 const GAMMA1_MASK_LEN: usize,
1663> Signer<SK, SK_LEN, SIG_LEN>
1664 for MLDSA<
1665 PK_LEN,
1666 SK_LEN,
1667 FULL_SK_LEN,
1668 SIG_LEN,
1669 PK,
1670 SK,
1671 TAU,
1672 LAMBDA,
1673 GAMMA1,
1674 GAMMA2,
1675 k,
1676 l,
1677 ETA,
1678 BETA,
1679 OMEGA,
1680 C_TILDE,
1681 POLY_Z_PACKED_LEN,
1682 POLY_W1_PACKED_LEN,
1683 S1_PACKED_LEN,
1684 S2_PACKED_LEN,
1685 T1_PACKED_LEN,
1686 LAMBDA_over_4,
1687 GAMMA1_MINUS_BETA,
1688 GAMMA2_MINUS_BETA,
1689 GAMMA1_MASK_LEN,
1690 >
1691{
1692 fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> {
1693 let mut out = [0u8; SIG_LEN];
1694 Self::sign_out(sk, msg, ctx, &mut out)?;
1695
1696 Ok(out)
1697 }
1698
1699 fn sign_out(
1700 sk: &SK,
1701 msg: &[u8],
1702 ctx: Option<&[u8]>,
1703 output: &mut [u8; SIG_LEN],
1704 ) -> Result<usize, SignatureError> {
1705 output.fill(0);
1706
1707 let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?;
1708 let bytes_written = Self::sign_mu_out(sk, &mu, output)?;
1709
1710 Ok(bytes_written)
1711 }
1712
1713 fn sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1714 Ok(Self {
1715 _phantom: PhantomData,
1716 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1717 signer_rnd: None,
1718 sk: Some(sk.clone()),
1719 seed: None,
1720 pk: None,
1721 })
1722 }
1723
1724 fn sign_update(&mut self, msg_chunk: &[u8]) {
1725 self.mu_builder.do_update(msg_chunk);
1726 }
1727
1728 fn sign_final(self) -> Result<[u8; SIG_LEN], SignatureError> {
1729 let mut out = [0u8; SIG_LEN];
1730 self.sign_final_out(&mut out)?;
1731 Ok(out)
1732 }
1733
1734 fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError> {
1735 let mu = self.mu_builder.do_final();
1736
1737 if self.sk.is_none() && self.seed.is_none() {
1738 return Err(SignatureError::GenericError(
1739 "Somehow you managed to construct a streaming signer without a private key, impressive!",
1740 ));
1741 }
1742
1743 output.fill(0);
1744
1745 if self.sk.is_some() {
1746 if self.signer_rnd.is_none() {
1747 Self::sign_mu_out(&self.sk.unwrap(), &mu, output)
1748 } else {
1749 Self::sign_mu_deterministic_out(
1750 &self.sk.unwrap(),
1751 &mu,
1752 self.signer_rnd.unwrap(),
1753 output,
1754 )
1755 }
1756 } else if self.seed.is_some() {
1757 let rnd = if self.signer_rnd.is_some() {
1758 self.signer_rnd.unwrap()
1759 } else {
1760 let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
1761 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
1762 rnd
1763 };
1764 Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output)
1765 } else {
1766 unreachable!()
1767 }
1768 }
1769}
1770
1771impl<
1772 const PK_LEN: usize,
1773 const SK_LEN: usize,
1774 const FULL_SK_LEN: usize,
1775 const SIG_LEN: usize,
1776 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN>
1777 + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
1778 SK: MLDSAPrivateKeyTrait<
1779 k,
1780 l,
1781 S1_PACKED_LEN,
1782 S2_PACKED_LEN,
1783 T1_PACKED_LEN,
1784 PK_LEN,
1785 SK_LEN,
1786 FULL_SK_LEN,
1787 > + MLDSAPrivateKeyInternalTrait<
1788 LAMBDA,
1789 GAMMA2,
1790 k,
1791 l,
1792 ETA,
1793 S1_PACKED_LEN,
1794 S2_PACKED_LEN,
1795 PK_LEN,
1796 SK_LEN,
1797 >,
1798 const TAU: i32,
1799 const LAMBDA: i32,
1800 const GAMMA1: i32,
1801 const GAMMA2: i32,
1802 const k: usize,
1803 const l: usize,
1804 const ETA: usize,
1805 const BETA: i32,
1806 const OMEGA: i32,
1807 const C_TILDE: usize,
1808 const POLY_Z_PACKED_LEN: usize,
1809 const POLY_W1_PACKED_LEN: usize,
1810 const S1_PACKED_LEN: usize,
1811 const S2_PACKED_LEN: usize,
1812 const T1_PACKED_LEN: usize,
1813 const LAMBDA_over_4: usize,
1814 const GAMMA1_MINUS_BETA: i32,
1815 const GAMMA2_MINUS_BETA: i32,
1816 const GAMMA1_MASK_LEN: usize,
1817> SignatureVerifier<PK, PK_LEN, SIG_LEN>
1818 for MLDSA<
1819 PK_LEN,
1820 SK_LEN,
1821 FULL_SK_LEN,
1822 SIG_LEN,
1823 PK,
1824 SK,
1825 TAU,
1826 LAMBDA,
1827 GAMMA1,
1828 GAMMA2,
1829 k,
1830 l,
1831 ETA,
1832 BETA,
1833 OMEGA,
1834 C_TILDE,
1835 POLY_Z_PACKED_LEN,
1836 POLY_W1_PACKED_LEN,
1837 S1_PACKED_LEN,
1838 S2_PACKED_LEN,
1839 T1_PACKED_LEN,
1840 LAMBDA_over_4,
1841 GAMMA1_MINUS_BETA,
1842 GAMMA2_MINUS_BETA,
1843 GAMMA1_MASK_LEN,
1844 >
1845{
1846 fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> {
1847 let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?;
1848
1849 if sig.len() != SIG_LEN {
1850 return Err(SignatureError::LengthError("Signature value is not the correct length."));
1851 }
1852 Self::verify_mu(pk, &mu, &sig.try_into().unwrap())
1853 }
1854
1855 fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1856 Ok(Self {
1857 _phantom: Default::default(),
1858 mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?,
1859 signer_rnd: None,
1860 sk: None,
1861 seed: None,
1862 pk: Some(pk.clone()),
1863 })
1864 }
1865
1866 fn verify_update(&mut self, msg_chunk: &[u8]) {
1867 self.mu_builder.do_update(msg_chunk);
1868 }
1869
1870 fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> {
1871 let mu = self.mu_builder.do_final();
1872
1873 assert!(
1874 self.pk.is_some(),
1875 "Somehow you managed to construct a streaming verifier without a public key, impressive!"
1876 );
1877
1878 if sig.len() != SIG_LEN {
1879 return Err(SignatureError::LengthError("Signature value is not the correct length."));
1880 }
1881
1882 Self::verify_mu(&self.pk.unwrap(), &mu, &sig.try_into().unwrap())
1883 }
1884}
1885
1886/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204.
1887/// Provides a stateful version of [`MLDSATrait::compute_mu_from_pk`] and [`MLDSATrait::compute_mu_from_tr`]
1888/// that supports streaming
1889/// large to-be-signed messages.
1890///
1891/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA
1892/// does not benefit from allowing external construction of the message representative mu.
1893/// It is possible to get the same behaviour by computing the pre-hash `ph` with the appropriate hash function
1894/// and providing that to HashMLDSA via [`PHSigner::sign_ph`].
1895#[derive(Clone)]
1896pub struct MuBuilder {
1897 h: H,
1898}
1899
1900impl MuBuilder {
1901 /// Algorithm 7
1902 /// 6: π β H(BytesToBits(π‘π)||πβ², 64)
1903 pub fn compute_mu(
1904 tr: &[u8; 64],
1905 msg: &[u8],
1906 ctx: Option<&[u8]>,
1907 ) -> Result<[u8; 64], SignatureError> {
1908 let mut mu_builder = MuBuilder::do_init(&tr, ctx)?;
1909 mu_builder.do_update(msg);
1910 let mu = mu_builder.do_final();
1911
1912 Ok(mu)
1913 }
1914
1915 /// This function requires the public key hash `tr`, which can be computed from the public key
1916 /// using [`MLDSAPublicKeyTrait::compute_tr`].
1917 pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1918 let ctx = match ctx {
1919 Some(ctx) => ctx,
1920 None => &[],
1921 };
1922
1923 // Algorithm 2
1924 // 1: if |ππ‘π₯| > 255 then
1925 if ctx.len() > 255 {
1926 return Err(SignatureError::LengthError("ctx value is longer than 255 bytes"));
1927 }
1928
1929 // Algorithm 7
1930 // 6: π β H(BytesToBits(π‘π)||π', 64)
1931 let mut mb = Self { h: H::new() };
1932 mb.h.absorb(tr).expect("absorb before squeeze is infallible");
1933
1934 // Algorithm 2
1935 // 10: πβ² β BytesToBits(IntegerToBytes(0, 1) β₯ IntegerToBytes(|ππ‘π₯|, 1) β₯ ππ‘π₯) β₯ π
1936 // all done together
1937 mb.h.absorb(&[0u8]).expect("absorb before squeeze is infallible");
1938 mb.h.absorb(&[ctx.len() as u8]).expect("absorb before squeeze is infallible");
1939 mb.h.absorb(ctx).expect("absorb before squeeze is infallible");
1940
1941 // now ready to absorb M
1942 Ok(mb)
1943 }
1944
1945 /// Stream a chunk of the message.
1946 pub fn do_update(&mut self, msg_chunk: &[u8]) {
1947 self.h.absorb(msg_chunk).expect("absorb before squeeze is infallible");
1948 }
1949
1950 /// Finalize and return the mu value.
1951 pub fn do_final(mut self) -> [u8; 64] {
1952 // Completion of
1953 // Algorithm 7
1954 // 6: π β H(BytesToBits(π‘π)||π β², 64)
1955 let mut mu = [0u8; 64];
1956 self.h.squeeze_out(&mut mu);
1957
1958 mu
1959 }
1960}
1961
1962/// The length, in bytes, of a serialized state of a [`MuBuilder`] object.
1963pub const SUSPENDED_MU_BUILDER_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN;
1964
1965/// If you are processing a large input message into ML-DSA and want to pause the operation
1966/// -- maybe while waiting for slow network IO), you'll need to use [`Suspendable`].
1967/// Serialization of the state of an in-progress ML-DSA instance is really just serialization
1968/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm
1969/// has a pausable state.
1970// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge,
1971// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl.
1972impl Suspendable<SUSPENDED_SHA3_STATE_LEN> for MuBuilder {
1973 fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] {
1974 self.h.suspend()
1975 }
1976
1977 fn from_suspended(
1978 serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN],
1979 ) -> Result<Self, SuspendableError> {
1980 Ok(MuBuilder { h: H::from_suspended(serialized_state)? })
1981 }
1982}