bouncycastle_mldsa/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 you need to sign or verify 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_interface::errors::SignatureError;
12//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
13//! use bouncycastle_core_interface::traits::Signature;
14//!
15//! let (pk, sk) = MLDSA65::keygen().unwrap();
16//!
17//! // Let's pretend this message was so long that you couldn't possibly
18//! // stream the whole thing over a network, and you need it 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: Vec<u8> = signer.sign_final().unwrap();
26//! // This is the signature value that you can save to a file or whatever you need.
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_interface::errors::SignatureError;
54//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
55//! use bouncycastle_core_interface::traits::Signature;
56//!
57//! let (pk, sk) = MLDSA65::keygen().unwrap();
58//!
59//! // Let's pretend this message was so long that you couldn't possibly
60//! // stream the whole thing over a network, and you need it 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: Vec<u8> = 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 you to know the public key that you 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, your 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_interface::errors::SignatureError;
100//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
101//! use bouncycastle_core_interface::traits::Signature;
102//!
103//! let (pk, _) = MLDSA65::keygen().unwrap();
104//!
105//! // Let's pretend this message was so long that you couldn't possibly
106//! // stream the whole thing over a network, and you need it pre-hashed.
107//! let msg = b"The quick brown fox jumped over the lazy dog";
108//!
109//! let mu: [u8; 64] = MuBuilder::compute_mu(msg, None, &pk.compute_tr()).unwrap();
110//! ```
111//!
112//! Note: if you are going to bind a `ctx` value (explained below), then you need to do in in [MuBuilder::compute_mu].
113//!
114//! If the message really is so huge that you can't hold it all in memory at once, then you might prefer a streaming API for
115//! computing mu:
116//!
117//! ```rust
118//! use bouncycastle_core_interface::errors::SignatureError;
119//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
120//! use bouncycastle_core_interface::traits::Signature;
121//!
122//! let (pk, _) = MLDSA65::keygen().unwrap();
123//!
124//! // Let's pretend this message was so long that you couldn't possibly
125//! // stream the whole thing over a network, and you need it 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, you can compute a signature that verifies as normal (no mu's required!):
136//!
137//! ```rust
138//! use bouncycastle_core_interface::errors::SignatureError;
139//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
140//! use bouncycastle_core_interface::traits::Signature;
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 sent to you.
147//! // They would have had to know pk!
148//! let mu: [u8; 64] = MuBuilder::compute_mu(msg, None, &pk.compute_tr()).unwrap();
149//!
150//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap();
151//! // This is the signature value that you can save to a file or whatever you 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 let you set the signing context value (`ctx`) and the signing nonce (`rnd`).
163//! Let's talk about them 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, `ctx` is one of those things that if you don't know what it does, then you're probably
178//! fine to ignore it.
179//!
180//! Example of signing and verifying with a `ctx` value:
181//!
182//! ```rust
183//! use bouncycastle_core_interface::errors::SignatureError;
184//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait};
185//! use bouncycastle_core_interface::traits::Signature;
186//!
187//! let msg = b"The quick brown fox";
188//! let ctx = b"FooTextDocumentFormat";
189//!
190//! let (pk, sk) = MLDSA65::keygen().unwrap();
191//!
192//! let sig: Vec<u8> = MLDSA65::sign(&sk, msg, Some(ctx)).unwrap();
193//! // This is the signature value that you can save to a file or whatever you need.
194//!
195//! match MLDSA65::verify(&pk, msg, Some(ctx), &sig) {
196//! Ok(()) => println!("Signature is valid!"),
197//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
198//! Err(e) => panic!("Something else went wrong: {:?}", e),
199//! }
200//! ```
201//!
202//! ## rnd
203//!
204//! This is the signature nonce, whose purpose is to ensure that you get different signature values
205//! if you sign the same message with the same public key multiple times.
206//!
207//! In general, the "deterministic" mode of ML-DSA (which usually uses an all-zero `rnd`) is considered
208//! secure and safe to use but you may lose certain privacy properties, because, for example,
209//! it becomes obvious that multiple identical signatures means that the same message was signed multiple times
210//! by the same private key.
211//!
212//! The default mode of ML-DSA uses a `rnd` generated by the library's OS-backed RNG, but you can set the `rnd`
213//! if you need to; for example if you are running on an embedded device that does not have access to an RNG.
214//!
215//! Note that in order to avoid combinatorial explosion of API functions, setting the `rnd` value is only
216//! available in conjunction with external mu or streaming modes. The example of setting `rnd` on the streaming
217//! API was shown above.
218//!
219//! Here is an example of using the [MLDSA::sign_mu_deterministic] function:
220//!
221//! ```rust
222//! use bouncycastle_core_interface::errors::SignatureError;
223//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
224//! use bouncycastle_core_interface::traits::Signature;
225//!
226//! let msg = b"The quick brown fox jumped over the lazy dog";
227//!
228//! let (pk, sk) = MLDSA65::keygen().unwrap();
229//!
230//! // Assume this was computed somewhere else and sent to you.
231//! // They would have had to know pk!
232//! let mu: [u8; 64] = MuBuilder::compute_mu(msg, None, &pk.compute_tr()).unwrap();
233//!
234//! // Typically, "deterministic" mode of ML-DSA will use an all-zero rnd,
235//! // but we've exposed it so you can set any value you need to.
236//! let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap();
237//! // This is the signature value that you can save to a file or whatever you need.
238//!
239//! match MLDSA65::verify(&pk, msg, None, &sig) {
240//! Ok(()) => println!("Signature is valid!"),
241//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"),
242//! Err(e) => panic!("Something else went wrong: {:?}", e),
243//! }
244//! ```
245//!
246//! # sign_from_seed
247//!
248//! This mode is intended for users with extreme performance or resource-limitation requirements.
249//!
250//! A very careful analysis of the ML-DSA signing algorithm will show that you don't actually need
251//! the entire ML-DSA private key to be in memory at the same time. In fact, it is possible to merge
252//! the keygen() and sign() functions
253//!
254//! We provide [MLDSA::sign_mu_deterministic_from_seed] which implements such an algorithm.
255//! It has a significantly lower peak-memory-footprint than the regular signing API (although there's
256//! always room for more optimization), and according to our benchmarks it is only around 25% slower
257//! than signing with a fully-expanded private key -- which is still faster than performing a full
258//! keygen followed by a regular sign since there are intermediate values common to keygen and sign
259//! that the merged function is able to only compute once.
260//!
261//! Since this is intended for hard-core embedded systems people, we have not wrapped this in all
262//! the beginner-friendly APIs. If you need this, then we assume you know what you're doing!
263//!
264//! Example usage:
265//!
266//! ```rust
267//! use bouncycastle_core_interface::errors::SignatureError;
268//! use bouncycastle_mldsa::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
269//! use bouncycastle_core_interface::traits::Signature;
270//! use bouncycastle_core_interface::traits::KeyMaterial;
271//! use bouncycastle_core_interface::key_material::{KeyMaterial256, KeyType};
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//! // At some point, you'll need to compute the public key, both to get `tr`, and so other
281//! // people can verify your signature.
282//! // There's no possible short-cut to efficiently computing the public key or `tr` from the seed;
283//! // you have to run the full keygen to get the full private key, at least momentarily, then
284//! // you can discard it in 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 and sent to you.
289//! // They would have had to know pk!
290//! let mu: [u8; 64] = MuBuilder::compute_mu(msg, None, &tr).unwrap();
291//! let rnd: [u8; 32] = [0u8; 32]; // with this API, you're responsible for your 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
312use std::marker::PhantomData;
313use crate::aux_functions::{expand_mask, expandA, expandS, make_hint_vecs, ntt, power_2_round_vec, sample_in_ball, sig_encode, sig_decode, use_hint_vecs};
314use crate::matrix::Vector;
315use crate::mldsa_keys::{MLDSAPublicKeyTrait, MLDSAPublicKeyInternalTrait};
316use crate::mldsa_keys::{MLDSAPrivateKeyTrait, MLDSAPrivateKeyInternalTrait};
317use crate::{MLDSA44PublicKey, MLDSA44PrivateKey, MLDSA65PublicKey, MLDSA65PrivateKey, MLDSA87PublicKey, MLDSA87PrivateKey};
318use bouncycastle_core_interface::errors::SignatureError;
319use bouncycastle_core_interface::key_material::{
320 KeyMaterial, KeyMaterial256, KeyMaterialSized, KeyType,
321};
322use bouncycastle_core_interface::traits::{RNG, SecurityStrength, XOF, Signature, Algorithm};
323use bouncycastle_rng::{HashDRBG_SHA512};
324use bouncycastle_sha3::{SHAKE128, SHAKE256};
325
326
327// imports needed just for docs
328#[allow(unused_imports)]
329use bouncycastle_core_interface::traits::PHSignature;
330#[allow(unused_imports)]
331use crate::hash_mldsa;
332
333/*** Constants ***/
334
335///
336pub const ML_DSA_44_NAME: &str = "ML-DSA-44";
337///
338pub const ML_DSA_65_NAME: &str = "ML-DSA-65";
339///
340pub const ML_DSA_87_NAME: &str = "ML-DSA-87";
341
342// From FIPS 204 Table 1 and Table 2
343
344// Constants that are the same for all parameter sets
345pub(crate) const N: usize = 256;
346pub(crate) const q: i32 = 8380417;
347pub(crate) const q_inv: i32 = 58728449; // q ^ (-1) mod 2 ^32
348pub(crate) const d: i32 = 13;
349/// Length of the \[u8] holding an ML-DSA seed value.
350pub const SEED_LEN: usize = 32;
351/// Length of the \[u8] holding an ML-DSA signing random value.
352pub const RND_LEN: usize = 32;
353/// Length of the \[u8] holding an ML-DSA tr value (which is the SHAKE256 hash of the public key).
354pub const TR_LEN: usize = 64;
355/// Length of the \[u8] holding an ML-DSA mu value.
356pub const MU_LEN: usize = 64;
357pub(crate) const POLY_T1PACKED_LEN: usize = 320;
358pub(crate) const POLY_T0PACKED_LEN: usize = 416;
359
360
361/* ML-DSA-44 params */
362
363/// Length of the \[u8] holding a ML-DSA-44 public key.
364pub const MLDSA44_PK_LEN: usize = 1312;
365/// Length of the \[u8] holding a ML-DSA-44 private key.
366pub const MLDSA44_SK_LEN: usize = 2560;
367/// Length of the \[u8] holding a ML-DSA-44 signature value.
368pub const MLDSA44_SIG_LEN: usize = 2420;
369pub(crate) const MLDSA44_TAU: i32 = 39;
370pub(crate) const MLDSA44_LAMBDA: i32 = 128;
371pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17;
372pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88;
373pub(crate) const MLDSA44_k: usize = 4;
374pub(crate) const MLDSA44_l: usize = 4;
375pub(crate) const MLDSA44_ETA: usize = 2;
376pub(crate) const MLDSA44_BETA: i32 = 78;
377pub(crate) const MLDSA44_OMEGA: i32 = 80;
378
379// Useful derived values
380pub(crate) const MLDSA44_C_TILDE: usize = 32;
381pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576;
382pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192;
383pub(crate) const MLDSA44_W1_PACKED_LEN: usize = MLDSA44_k * MLDSA44_POLY_W1_PACKED_LEN;
384pub(crate) const MLDSA44_POLY_ETA_PACKED_LEN: usize = 32*3;
385pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128/4;
386
387// Alg 32
388// 1: π β 1 + bitlen (πΎ1 β 1)
389pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (πΎ1 β 1) )
390
391
392/* ML-DSA-65 params */
393
394/// Length of the \[u8] holding a ML-DSA-65 public key.
395pub const MLDSA65_PK_LEN: usize = 1952;
396/// Length of the \[u8] holding a ML-DSA-65 private key.
397pub const MLDSA65_SK_LEN: usize = 4032;
398/// Length of the \[u8] holding a ML-DSA-65 signature value.
399pub const MLDSA65_SIG_LEN: usize = 3309;
400pub(crate) const MLDSA65_TAU: i32 = 49;
401pub(crate) const MLDSA65_LAMBDA: i32 = 192;
402pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19;
403pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32;
404pub(crate) const MLDSA65_k: usize = 6;
405pub(crate) const MLDSA65_l: usize = 5;
406pub(crate) const MLDSA65_ETA: usize = 4;
407pub(crate) const MLDSA65_BETA: i32 = 196;
408pub(crate) const MLDSA65_OMEGA: i32 = 55;
409
410// Useful derived values
411pub(crate) const MLDSA65_C_TILDE: usize = 48;
412pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640;
413pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128;
414pub(crate) const MLDSA65_W1_PACKED_LEN: usize = MLDSA65_k * MLDSA65_POLY_W1_PACKED_LEN;
415pub(crate) const MLDSA65_POLY_ETA_PACKED_LEN: usize = 32*4;
416pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192/4;
417
418// Alg 32
419// 1: π β 1 + bitlen (πΎ1 β 1)
420pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640;
421
422
423
424/* ML-DSA-87 params */
425
426/// Length of the \[u8] holding a ML-DSA-87 public key.
427pub const MLDSA87_PK_LEN: usize = 2592;
428/// Length of the \[u8] holding a ML-DSA-87 private key.
429pub const MLDSA87_SK_LEN: usize = 4896;
430/// Length of the \[u8] holding a ML-DSA-87 signature value.
431pub const MLDSA87_SIG_LEN: usize = 4627;
432pub(crate) const MLDSA87_TAU: i32 = 60;
433pub(crate) const MLDSA87_LAMBDA: i32 = 256;
434pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19;
435pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32;
436pub(crate) const MLDSA87_k: usize = 8;
437pub(crate) const MLDSA87_l: usize = 7;
438pub(crate) const MLDSA87_ETA: usize = 2;
439pub(crate) const MLDSA87_BETA: i32 = 120;
440pub(crate) const MLDSA87_OMEGA: i32 = 75;
441
442// Useful derived values
443pub(crate) const MLDSA87_C_TILDE: usize = 64;
444pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640;
445pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128;
446pub(crate) const MLDSA87_W1_PACKED_LEN: usize = MLDSA87_k * MLDSA87_POLY_W1_PACKED_LEN;
447pub(crate) const MLDSA87_POLY_ETA_PACKED_LEN: usize = 32*3;
448pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256/4;
449
450// Alg 32
451// 1: π β 1 + bitlen (πΎ1 β 1)
452pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640;
453
454
455
456// Typedefs just to make the algorithms look more like the FIPS 204 sample code.
457pub(crate) type H = SHAKE256;
458pub(crate) type G = SHAKE128;
459
460
461/*** Pub Types ***/
462
463/// The ML-DSA-44 algorithm.
464pub type MLDSA44 = MLDSA<
465 MLDSA44_PK_LEN,
466 MLDSA44_SK_LEN,
467 MLDSA44_SIG_LEN,
468 MLDSA44PublicKey,
469 MLDSA44PrivateKey,
470 MLDSA44_TAU,
471 MLDSA44_LAMBDA,
472 MLDSA44_GAMMA1,
473 MLDSA44_GAMMA2,
474 MLDSA44_k,
475 MLDSA44_l,
476 MLDSA44_ETA,
477 MLDSA44_BETA,
478 MLDSA44_OMEGA,
479 MLDSA44_C_TILDE,
480 MLDSA44_POLY_Z_PACKED_LEN,
481 MLDSA44_POLY_W1_PACKED_LEN,
482 MLDSA44_W1_PACKED_LEN,
483 MLDSA44_POLY_ETA_PACKED_LEN,
484 MLDSA44_LAMBDA_over_4,
485 MLDSA44_GAMMA1_MASK_LEN,
486>;
487
488impl Algorithm for MLDSA44 {
489 const ALG_NAME: &'static str = ML_DSA_44_NAME;
490 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
491}
492
493/// The ML-DSA-65 algorithm.
494pub type MLDSA65 = MLDSA<
495 MLDSA65_PK_LEN,
496 MLDSA65_SK_LEN,
497 MLDSA65_SIG_LEN,
498 MLDSA65PublicKey,
499 MLDSA65PrivateKey,
500 MLDSA65_TAU,
501 MLDSA65_LAMBDA,
502 MLDSA65_GAMMA1,
503 MLDSA65_GAMMA2,
504 MLDSA65_k,
505 MLDSA65_l,
506 MLDSA65_ETA,
507 MLDSA65_BETA,
508 MLDSA65_OMEGA,
509 MLDSA65_C_TILDE,
510 MLDSA65_POLY_Z_PACKED_LEN,
511 MLDSA65_POLY_W1_PACKED_LEN,
512 MLDSA65_W1_PACKED_LEN,
513 MLDSA65_POLY_ETA_PACKED_LEN,
514 MLDSA65_LAMBDA_over_4,
515 MLDSA65_GAMMA1_MASK_LEN,
516>;
517
518impl Algorithm for MLDSA65 {
519 const ALG_NAME: &'static str = ML_DSA_65_NAME;
520 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
521}
522
523/// The ML-DSA-87 algorithm.
524pub type MLDSA87 = MLDSA<
525 MLDSA87_PK_LEN,
526 MLDSA87_SK_LEN,
527 MLDSA87_SIG_LEN,
528 MLDSA87PublicKey,
529 MLDSA87PrivateKey,
530 MLDSA87_TAU,
531 MLDSA87_LAMBDA,
532 MLDSA87_GAMMA1,
533 MLDSA87_GAMMA2,
534 MLDSA87_k,
535 MLDSA87_l,
536 MLDSA87_ETA,
537 MLDSA87_BETA,
538 MLDSA87_OMEGA,
539 MLDSA87_C_TILDE,
540 MLDSA87_POLY_Z_PACKED_LEN,
541 MLDSA87_POLY_W1_PACKED_LEN,
542 MLDSA87_W1_PACKED_LEN,
543 MLDSA87_POLY_ETA_PACKED_LEN,
544 MLDSA87_LAMBDA_over_4,
545 MLDSA87_GAMMA1_MASK_LEN,
546>;
547
548impl Algorithm for MLDSA87 {
549 const ALG_NAME: &'static str = ML_DSA_87_NAME;
550 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
551}
552
553/// The core internal implementation of the ML-DSA algorithm.
554/// This needs to be public for the compiler to be able to find it, but you shouldn't ever
555/// need to use this directly. Please use the named public types.
556pub struct MLDSA<
557 const PK_LEN: usize,
558 const SK_LEN: usize,
559 const SIG_LEN: usize,
560 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
561 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
562 const TAU: i32,
563 const LAMBDA: i32,
564 const GAMMA1: i32,
565 const GAMMA2: i32,
566 const k: usize,
567 const l: usize,
568 const ETA: usize,
569 const BETA: i32,
570 const OMEGA: i32,
571 const C_TILDE: usize,
572 const POLY_VEC_H_PACKED_LEN: usize,
573 const POLY_W1_PACKED_LEN: usize,
574 const W1_PACKED_LEN: usize,
575 const POLY_ETA_PACKED_LEN: usize,
576 const LAMBDA_over_4: usize,
577 const GAMMA1_MASK_LEN: usize,
578> {
579 _phantom: PhantomData<(PK, SK)>,
580
581 /// used for streaming the message for both signing and verifying
582 mu_builder: MuBuilder,
583
584 signer_rnd: Option<[u8; RND_LEN]>,
585
586 /// only used in streaming sign operations
587 sk: Option<SK>,
588
589 /// only used in streaming sign operations instead of sk
590 seed: Option<KeyMaterialSized<32>>,
591
592 /// only used in streaming verify operations
593 pk: Option<PK>,
594}
595
596impl<
597 const PK_LEN: usize,
598 const SK_LEN: usize,
599 const SIG_LEN: usize,
600 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
601 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
602 const TAU: i32,
603 const LAMBDA: i32,
604 const GAMMA1: i32,
605 const GAMMA2: i32,
606 const k: usize,
607 const l: usize,
608 const ETA: usize,
609 const BETA: i32,
610 const OMEGA: i32,
611 const C_TILDE: usize,
612 const POLY_Z_PACKED_LEN: usize,
613 const POLY_W1_PACKED_LEN: usize,
614 const W1_PACKED_LEN: usize,
615 const POLY_ETA_PACKED_LEN: usize,
616 const LAMBDA_over_4: usize,
617 const GAMMA1_MASK_LEN: usize,
618> MLDSA<
619 PK_LEN,
620 SK_LEN,
621 SIG_LEN,
622 PK,
623 SK,
624 TAU,
625 LAMBDA,
626 GAMMA1,
627 GAMMA2,
628 k,
629 l,
630 ETA,
631 BETA,
632 OMEGA,
633 C_TILDE,
634 POLY_Z_PACKED_LEN,
635 POLY_W1_PACKED_LEN,
636 W1_PACKED_LEN,
637 POLY_ETA_PACKED_LEN,
638 LAMBDA_over_4,
639 GAMMA1_MASK_LEN,
640>
641{
642 /// Should still be ok in FIPS mode
643 pub fn keygen_from_os_rng() -> Result<
644 (PK, SK),
645 SignatureError,
646 > {
647 let mut seed = KeyMaterial256::new();
648 HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?;
649 Self::keygen_internal(&seed)
650 }
651 /// Implements Algorithm 6 of FIPS 204
652 /// Note: NIST has made a special exception in the FIPS 204 FAQ that this _internal function
653 /// may in fact be exposed outside the crypto module.
654 ///
655 /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one
656 /// specifically takes a 32-byte [KeyMaterial256] and checks that it has [KeyType::Seed] and
657 /// [SecurityStrength::_256bit].
658 /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it using
659 /// [KeyMaterial::from_key]
660 pub(crate) fn keygen_internal(
661 seed: &KeyMaterial256,
662 ) -> Result<
663 (PK, SK),
664 SignatureError,
665 > {
666 if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy)
667 || seed.key_len() != 32
668 {
669 return Err(SignatureError::KeyGenError(
670 "Seed must be 32 bytes and KeyType::Seed or KeyType::BytesFullEntropy.",
671 ));
672 }
673
674 if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
675 return Err(SignatureError::KeyGenError("Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87)."));
676 }
677
678 // Alg 6 line 1: (rho, rho_prime, K) <- H(π||IntegerToBytes(π, 1)||IntegerToBytes(β, 1), 128)
679 // β· expand seed
680 let mut rho: [u8; 32] = [0u8; 32];
681 let mut K: [u8; 32] = [0u8; 32];
682
683 let (s1, s2) = { // scope for h
684 let mut h = H::default();
685 h.absorb(seed.ref_to_bytes());
686 h.absorb(&(k as u8).to_le_bytes());
687 h.absorb(&(l as u8).to_le_bytes());
688 let bytes_written = h.squeeze_out(&mut rho);
689 debug_assert_eq!(bytes_written, 32);
690 let mut rho_prime: [u8; 64] = [0u8; 64];
691 let bytes_written = h.squeeze_out(&mut rho_prime);
692 debug_assert_eq!(bytes_written, 64);
693 let bytes_written = h.squeeze_out(&mut K);
694 debug_assert_eq!(bytes_written, 32);
695
696 // 4: (π¬1, π¬2) β ExpandS(πβ²)
697 let (s1, s2) = expandS::<k, l, ETA>(&rho_prime);
698
699 // Clear the secret data before returning memory to the OS
700 rho_prime.fill(0u8);
701 (s1, s2)
702 };
703
704 // 3: π_hat β ExpandA(π) β· π is generated and stored in NTT representation as π
705 let A_hat = expandA::<k, l>(&rho);
706
707 let t_hat = { // scope for s1_hat
708 // 5: π β NTTβ1(π β NTT(π¬1)) + π¬2
709 // β· compute π = ππ¬1 + π¬2
710 let mut s1_hat = s1.clone();
711 s1_hat.ntt();
712 A_hat.matrix_vector_ntt(&s1_hat)
713 };
714
715 let (t1, t0) = { // scope for t
716 let mut t = t_hat;
717 t.inv_ntt();
718 t.add_vector_ntt(&s2);
719 t.conditional_add_q();
720
721 // 6: (π1, π0) β Power2Round(π)
722 // β· compress π
723 // β· PowerTwoRound is applied componentwise (see explanatory text in Section 7.4)
724 power_2_round_vec::<k>(&t)
725 };
726
727 // 8: ππ β pkEncode(π, π1)
728 let pk = PK::new(&rho, &t1);
729
730 // 9: π‘π β H(ππ, 64)
731 let tr = pk.compute_tr();
732
733 // 10: π π β skEncode(π, πΎ, π‘π, π¬1, π¬2, π0)
734 // β· πΎ and π‘π are for use in signing
735 let sk = SK::new(&rho, &K, &tr, &s1, &s2, &t0, Some(seed.clone()));
736
737 // Clear the secret data before returning memory to the OS
738 // (SK::new() copies all values)
739 rho.fill(0u8);
740 K.fill(0u8);
741 // tr is public data, does not need to be zeroized
742 // s1, s2, t0 are all Vectors of Polynomials, so implement a zeroizing Drop
743
744 // 11: return (ππ, π π)
745 Ok((pk, sk))
746 }
747}
748
749impl<
750 const PK_LEN: usize,
751 const SK_LEN: usize,
752 const SIG_LEN: usize,
753 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
754 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
755 const TAU: i32,
756 const LAMBDA: i32,
757 const GAMMA1: i32,
758 const GAMMA2: i32,
759 const k: usize,
760 const l: usize,
761 const ETA: usize,
762 const BETA: i32,
763 const OMEGA: i32,
764 const C_TILDE: usize,
765 const POLY_Z_PACKED_LEN: usize,
766 const POLY_W1_PACKED_LEN: usize,
767 const W1_PACKED_LEN: usize,
768 const POLY_ETA_PACKED_LEN: usize,
769 const LAMBDA_over_4: usize,
770 const GAMMA1_MASK_LEN: usize,
771> MLDSATrait<PK_LEN, SK_LEN, SIG_LEN, PK, SK, k, l, ETA> for MLDSA<
772 PK_LEN,
773 SK_LEN,
774 SIG_LEN,
775 PK,
776 SK,
777 TAU,
778 LAMBDA,
779 GAMMA1,
780 GAMMA2,
781 k,
782 l,
783 ETA,
784 BETA,
785 OMEGA,
786 C_TILDE,
787 POLY_Z_PACKED_LEN,
788 POLY_W1_PACKED_LEN,
789 W1_PACKED_LEN,
790 POLY_ETA_PACKED_LEN,
791 LAMBDA_over_4,
792 GAMMA1_MASK_LEN,
793> {
794 /*** Key Generation and PK / SK consistency checks ***/
795
796 /// Imports a secret key from a seed.
797 fn keygen_from_seed(seed: &KeyMaterialSized<32>) -> Result<(PK, SK), SignatureError> {
798 Self::keygen_internal(seed)
799 }
800 /// Imports a secret key from both a seed and an encoded_sk.
801 ///
802 /// This is a convenience function to expand the key from seed and compare it against
803 /// the provided `encoded_sk` using a constant-time equality check.
804 /// If everything checks out, the secret key is returned fully populated with pk and seed.
805 /// If the provided key and derived key don't match, an error is returned.
806 fn keygen_from_seed_and_encoded(
807 seed: &KeyMaterialSized<32>,
808 encoded_sk: &[u8; SK_LEN],
809 ) -> Result<
810 (PK, SK),
811 SignatureError,
812 > {
813 let (pk, sk) = Self::keygen_internal(seed)?;
814
815 let sk_from_bytes = SK::sk_decode(encoded_sk);
816
817 // MLDSAPrivateKey impls PartialEq with a constant-time equality check.
818 if sk != sk_from_bytes {
819 return Err(SignatureError::KeyGenError("Encoded key does not match generated key"));
820 }
821
822 Ok((pk, sk))
823 }
824 /// Given a public key and a secret key, check that the public key matches the secret key.
825 /// This is a sanity check that the public key was generated correctly from the secret key.
826 ///
827 /// At the current time, this is only possible if `sk` either contains a public key (in which case
828 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
829 /// (in which case a keygen_from_seed is run and then the pk's compared).
830 ///
831 /// Returns either `()` or [SignatureError::ConsistencyCheckFailed].
832 fn keypair_consistency_check(
833 pk: &PK,
834 sk: &SK,
835 ) -> Result<(), SignatureError> {
836 // This is maybe a computationally heavy way to compare them, but it works
837 let derived_pk = sk.derive_pk();
838 if derived_pk.compute_tr() == pk.compute_tr() {
839 Ok(())
840 } else {
841 Err(SignatureError::ConsistencyCheckFailed())
842 }
843 }
844 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
845 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
846 ///
847 /// This function, together with [MLDSATrait::sign_mu] perform a complete ML-DSA signature which is indistinguishable
848 /// from one produced by the one-shot sign APIs.
849 ///
850 /// The utility of this function is exactly as described
851 /// on Line 6 of Algorithm 7 of FIPS 204:
852 ///
853 /// message representative that may optionally be computed in a different cryptographic module
854 ///
855 /// The utility is when an extremely large message needs to be signed, where the message exists on one
856 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
857 /// causes operational concerns (this is common for example with network HSMs or sending large messages
858 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
859 /// contents of the message are sensitive and the signer does not want to transmit the message itself
860 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
861 ///
862 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
863 /// can be used with many hardware crypto modules.
864 ///
865 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
866 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
867 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
868 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
869 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
870 /// ML-DSA verifier.
871 ///
872 /// This function requires the public key hash `tr`, which can be computed from the public key
873 /// using [MLDSAPublicKeyTrait::compute_tr].
874 ///
875 /// For a streaming version of this, see [MuBuilder].
876 fn compute_mu_from_tr(
877 msg: &[u8],
878 ctx: Option<&[u8]>,
879 tr: &[u8; 64],
880 ) -> Result<[u8; 64], SignatureError> {
881 MuBuilder::compute_mu(msg, ctx, tr)
882 }
883 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the public key.
884 fn compute_mu_from_pk(
885 msg: &[u8],
886 ctx: Option<&[u8]>,
887 pk: &PK,
888 ) -> Result<[u8; 64], SignatureError> {
889 MuBuilder::compute_mu(msg, ctx, &pk.compute_tr())
890 }
891 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the private key.
892 fn compute_mu_from_sk(
893 msg: &[u8],
894 ctx: Option<&[u8]>,
895 sk: &SK,
896 ) -> Result<[u8; 64], SignatureError> {
897 MuBuilder::compute_mu(msg, ctx, &sk.tr())
898 }
899 /// Performs an ML-DSA signature using the provided external message representative `mu`.
900 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
901 /// FIPS 204 itself, as well as subsequent FAQ documents.
902 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
903 fn sign_mu(
904 sk: &SK,
905 mu: &[u8; 64],
906 ) -> Result<[u8; SIG_LEN], SignatureError> {
907 let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN];
908 Self::sign_mu_out(sk, mu, &mut out)?;
909 Ok(out)
910 }
911 /// Performs an ML-DSA signature using the provided external message representative `mu`.
912 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
913 /// FIPS 204 itself, as well as subsequent FAQ documents.
914 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
915 ///
916 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
917 fn sign_mu_out(
918 sk: &SK,
919 mu: &[u8; 64],
920 output: &mut [u8; SIG_LEN],
921 ) -> Result<usize, SignatureError> {
922 let mut rnd: [u8; RND_LEN] = [0u8; RND_LEN];
923 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
924
925 Self::sign_mu_deterministic_out(sk, mu, rnd, output)
926 }
927 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
928 /// (modified to take an externally-computed mu instead of M')
929 ///
930 /// Performs an ML-DSA signature using the provided external message representative `mu`.
931 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
932 /// FIPS 204 itself, as well as subsequent FAQ documents.
933 ///
934 /// Security note:
935 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
936 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
937 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
938 /// If not, you may lose some privacy properties; for example it becomes easy to tell if a signer
939 /// has signed the same message twice or two different messagase, or to tell if the same message
940 /// has been signed by the same signer twice or two different signers.
941 ///
942 /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help
943 /// prevent accidental nonce reuse, this function moves `rnd`.
944 fn sign_mu_deterministic(
945 sk: &SK,
946 mu: &[u8; 64],
947 rnd: [u8; 32],
948 ) -> Result<[u8; SIG_LEN], SignatureError> {
949 let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN];
950 Self::sign_mu_deterministic_out(sk, mu, rnd, &mut out)?;
951 Ok(out)
952 }
953 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
954 /// (modified to take an externally-computed mu instead of M')
955 ///
956 /// Performs an ML-DSA signature using the provided external message representative `mu`.
957 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
958 /// FIPS 204 itself, as well as subsequent FAQ documents.
959 /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG.
960 ///
961 /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help
962 /// prevent accidental nonce reuse, this function moves `rnd`.
963 ///
964 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
965 fn sign_mu_deterministic_out(
966 sk: &SK,
967 mu: &[u8; 64],
968 rnd: [u8; 32],
969 output: &mut [u8; SIG_LEN],
970 ) -> Result<usize, SignatureError> {
971 // 1: (π, πΎ, π‘π, π¬1, π¬2, π0) β skDecode(π π)
972 // Already done -- the sk struct is already decoded
973
974 // 2: π¬1Μ_hat β NTT(π¬1)
975 let mut s1_hat = sk.s1().clone();
976 s1_hat.ntt();
977
978 // 3: π¬2Μ_hat β NTT(π¬2)
979 let mut s2_hat = sk.s2().clone();
980 s2_hat.ntt();
981
982 // 4: π0Μ_hat β NTT(π0)Μ
983 let mut t0_hat = sk.t0().clone();
984 t0_hat.ntt();
985
986 // 5: π_hat β ExpandA(π)
987 let A_hat = expandA::<k, l>(&sk.rho());
988
989 // 6: π β H(BytesToBits(π‘π)||π β², 64)
990 // skip: mu has already been provided
991
992 let mut rho_p_p:[u8; 64] = { // scope for h
993 // 7: πβ³ β H(πΎ||πππ||π, 64)
994 let mut h = H::new();
995 h.absorb(sk.K());
996 h.absorb(&rnd);
997 h.absorb(mu);
998 let mut rho_p_p = [0u8; 64];
999 h.squeeze_out(&mut rho_p_p);
1000 rho_p_p
1001 };
1002
1003 // 8: π
β 0
1004 // β· initialize counter π
1005 let mut kappa: u16 = 0;
1006
1007 // 9: (π³, π‘) β β₯
1008 // handled in the loop
1009
1010 // 10: while (π³, π‘) = β₯ do
1011 // β· rejection sampling loop
1012
1013 // these need to be outside the loop because they form the encoded signature value
1014 let mut sig_val_c_tilde = [0u8; LAMBDA_over_4];
1015 let mut sig_val_z: Vector<l>;
1016 let mut sig_val_h: Vector<k>;
1017 loop {
1018 // FIPS 204 s. 6.2 allows:
1019 // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value."
1020 if kappa > 1000 * k as u16 { return Err(SignatureError::GenericError("Rejection sampling loop exceeded max iterations, try again with a different signing nonce.")) }
1021
1022 // 11: π² β π
^β β ExpandMask(πβ³, π
)
1023 let mut y = expand_mask::<l, GAMMA1, GAMMA1_MASK_LEN>(&rho_p_p, kappa);
1024
1025 let w = { // scope for y_hat
1026 // 12: π° β NTTβ1(π_hat * NTT(π²))
1027 let mut y_hat = y.clone();
1028 y_hat.ntt();
1029 let mut w = A_hat.matrix_vector_ntt(&y_hat);
1030 w.inv_ntt();
1031 w.conditional_add_q();
1032 w
1033 };
1034
1035 // 13: π°1 β HighBits(π°)
1036 // β· signerβs commitment
1037 let w1 = w.high_bits::<GAMMA2>();
1038
1039 { // scope for h
1040 // 15: π_tilde β H(π||w1Encode(π°1), π/4)
1041 // β· commitment hash
1042 let mut hash = H::new();
1043 hash.absorb(mu);
1044 w1.w1_encode_and_hash::<W1_PACKED_LEN, POLY_W1_PACKED_LEN>(&mut hash);
1045 hash.squeeze_out(&mut sig_val_c_tilde);
1046 }
1047
1048 // 16: π β π
π β SampleInBall(c_tilde)
1049 // β· verifierβs challenge
1050 let c_hat = { // scope for c
1051 let c = sample_in_ball::<LAMBDA_over_4, TAU>(&sig_val_c_tilde);
1052
1053 // 17: π_hat β NTT(π)
1054 ntt(&c)
1055 };
1056 // 18: β¨β¨ππ¬1β©β© β NTTβ1(π_hat * π¬1_hat)
1057 // Note: <<.>> in FIPS 204 means that this value will be used again later, so you should hang on to it.
1058 let mut cs1 = s1_hat.scalar_vector_ntt(&c_hat);
1059 cs1.inv_ntt();
1060
1061 // 20: π³ β π² + β¨β¨ππ¬1β©β©
1062 y.add_vector_ntt(&cs1);
1063 sig_val_z = y;
1064
1065 // 23 (first half): if ||π³||β β₯ πΎ1 β π½ or ||π«0||β β₯ πΎ2 β π½ then (z, h) β β₯
1066 // β· validity checks
1067 // out-of-order on purpose for performance reasons:
1068 // might as well do the rejection sampling check before any extra heavy computation
1069 if sig_val_z.check_norm(GAMMA1 - BETA) {
1070 kappa += l as u16;
1071 continue;
1072 };
1073
1074 // 19: β¨β¨ππ¬2β©β© β NTTβ1(π_hat * π¬2Μ_hat)
1075 let mut cs2 = s2_hat.scalar_vector_ntt(&c_hat);
1076 cs2.inv_ntt();
1077
1078 // 21: π«0 β LowBits(π° β β¨β¨ππ¬2β©β©)
1079 let mut r0 = w.sub_vector(&cs2).low_bits::<GAMMA2>();
1080
1081 // 23 (second half): if ||π³||β β₯ πΎ1 β π½ or ||π«0||β β₯ πΎ2 β π½ then (z, h) β β₯
1082 // β· validity checks
1083 if r0.check_norm(GAMMA2 - BETA) {
1084 kappa += l as u16;
1085 continue;
1086 };
1087
1088 // 25: β¨β¨ππ0β©β© β NTTβ1(π_hat * π0Μ_hat )
1089 let mut ct0 = t0_hat.scalar_vector_ntt(&c_hat);
1090 ct0.inv_ntt();
1091
1092 // 28 (first half): if ||β¨β¨ππ0β©β©||β β₯ πΎ2 or the number of 1βs in π‘ is greater than π, then (z, h) β β₯
1093 // out-of-order on purpose for performance reasons:
1094 // might as well do the rejection sampling check before any extra heavy computation
1095 if ct0.check_norm(GAMMA2) {
1096 kappa += l as u16;
1097 continue;
1098 };
1099
1100 // 26: π‘ β MakeHint(ββ¨β¨ππ0β©β©, π° β β¨β¨ππ¬2β©β© + β¨β¨ππ0β©β©)
1101 // β· Signerβs hint
1102 r0.add_vector_ntt(&ct0);
1103 r0.conditional_add_q();
1104 let hint_hamming_weight: i32;
1105 sig_val_h = { // scope for hint
1106 let (hint, inner_hint_hamming_weight) =
1107 make_hint_vecs::<k, GAMMA2>(&r0, &w1);
1108 hint_hamming_weight = inner_hint_hamming_weight;
1109 hint
1110 };
1111
1112 // 28 (second half): if ||β¨β¨ππ0β©β©||β β₯ πΎ2 or the number of 1βs in π‘ is greater than π, then (z, h) β β₯
1113 if hint_hamming_weight > OMEGA {
1114 kappa += l as u16;
1115 continue;
1116 };
1117
1118 // "In addition, there is an alternative way of implementing the validity checks on π³ and the computation of
1119 // π‘, which is described in Section 5.1 of [6] (dilithium-specification-round3-20210208.pdf).
1120 // This method may also be used in implementations of ML-DSA."
1121 // todo -- I believe this code is already using this optimization, but it could use a deeper look to see if more optimization is possible.
1122
1123 break;
1124 }
1125
1126 // zeroize rho_p_p before returning it to the OS
1127 rho_p_p.fill(0u8);
1128
1129 // sig_encode does not necessarily write to all bytes of the output, so just to be safe:
1130 output.fill(0u8);
1131
1132 // 33: π β sigEncode(π, π³Μ modΒ±π, π‘)
1133 let bytes_written = sig_encode::<GAMMA1, k, l, LAMBDA_over_4, OMEGA, POLY_Z_PACKED_LEN, SIG_LEN>
1134 (&sig_val_c_tilde, &sig_val_z, &sig_val_h, output);
1135
1136 Ok(bytes_written)
1137 }
1138
1139 fn sign_mu_deterministic_from_seed(seed: &KeyMaterialSized<32>, mu: &[u8; 64], rnd: [u8; 32]) -> Result<[u8; SIG_LEN], SignatureError> {
1140 let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN];
1141 Self::sign_mu_deterministic_from_seed_out(seed, mu, rnd, &mut out)?;
1142 Ok(out)
1143 }
1144
1145 fn sign_mu_deterministic_from_seed_out(
1146 seed: &KeyMaterialSized<32>,
1147 mu: &[u8; 64],
1148 rnd: [u8; 32],
1149 output: &mut [u8; SIG_LEN],
1150 ) -> Result<usize, SignatureError> {
1151 // This function is a mash-up of keyGen (Algorithm 6) and sign (Algorithm 7)
1152
1153 // I have tried to keep this as clean as possible for correspondence with the FIPS,
1154 // but I have moved things around so that I can use unnamed scopes to limit how many
1155 // stack variables are alive at the same time.
1156
1157 // 1: (π, πΎ, π‘π, π¬1, π¬2, π0) β skDecode(π π)
1158 // to avoid having all of it in memory at the same time,
1159 // we're gonna derive what we need as we need it.
1160
1161 if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy)
1162 || seed.key_len() != 32
1163 {
1164 return Err(SignatureError::KeyGenError(
1165 "Seed must be 32 bytes and KeyType::Seed or KeyType::BytesFullEntropy.",
1166 ));
1167 }
1168
1169 if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
1170 return Err(SignatureError::KeyGenError("Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87)."));
1171 }
1172
1173 // Alg 7; 6: π β H(BytesToBits(π‘π)||π β², 64)
1174 // skip: mu has already been provided
1175
1176 let rho: [u8; 32];
1177 let mut rho_p_p:[u8; 64];
1178 let (s1, s2) = { // scope for h
1179 // derive sk.K
1180 // Alg 6; 1: (rho, rho_prime, K) <- H(π||IntegerToBytes(π, 1)||IntegerToBytes(β, 1), 128)
1181 // β· expand seed
1182 let mut tmp_rho: [u8; 32] = [0u8; 32];
1183 let mut rho_prime: [u8; 64] = [0u8; 64];
1184 let mut K: [u8; 32] = [0u8; 32];
1185
1186 let mut h = H::default();
1187 h.absorb(seed.ref_to_bytes());
1188 h.absorb(&(k as u8).to_le_bytes());
1189 h.absorb(&(l as u8).to_le_bytes());
1190 let bytes_written = h.squeeze_out(&mut tmp_rho);
1191 debug_assert_eq!(bytes_written, 32);
1192 let bytes_written = h.squeeze_out(&mut rho_prime);
1193 debug_assert_eq!(bytes_written, 64);
1194 let bytes_written = h.squeeze_out(&mut K);
1195 debug_assert_eq!(bytes_written, 32);
1196 rho = tmp_rho;
1197
1198
1199 // Alg 7; 7: πβ³ β H(πΎ||πππ||π, 64)
1200 let mut h = H::new();
1201 h.absorb(&K);
1202 h.absorb(&rnd);
1203 h.absorb(mu);
1204 let mut tmp_rho_p_p = [0u8; 64];
1205 h.squeeze_out(&mut tmp_rho_p_p);
1206 rho_p_p = tmp_rho_p_p;
1207
1208 // 4: (π¬1, π¬2) β ExpandS(πβ²)
1209 expandS::<k, l, ETA>(&rho_prime)
1210 };
1211
1212 // Alg 7; 5: π_hat β ExpandA(π)
1213 // Note on memory optimization:
1214 // A_hat consumes a large bit of memory and technically could move inside the loop --
1215 // -- or even more aggressively, could be derived and multiplied by y_hat row-by-row --
1216 // But in my unit tests, I see the loop typically execute 1 - 3 times, sometimes as many
1217 // as 20 or even 80 times. So moving expandA() inside the loop would be a pretty drastic speed-for-memory tradeoff
1218 // that I'm not willing to make in general, so I leave that as an optimization that people
1219 // can make on a private fork if you really really need the memory squeeze.
1220 let A_hat = expandA::<k, l>(&rho);
1221
1222 // Alg 7; 8: π
β 0
1223 // β· initialize counter π
1224 let mut kappa: u16 = 0;
1225
1226 // Alg 7; 9: (π³, π‘) β β₯
1227 // handled in the loop
1228
1229 // Alg 7; 10: while (π³, π‘) = β₯ do
1230 // β· rejection sampling loop
1231
1232 // these need to be outside the loop because they form the encoded signature value
1233 let mut sig_val_c_tilde = [0u8; LAMBDA_over_4];
1234 let mut sig_val_z: Vector<l>;
1235 let mut sig_val_h: Vector<k>;
1236 loop {
1237 // FIPS 204 s. 6.2 allows:
1238 // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value."
1239 if kappa > 1000 * k as u16 { return Err(SignatureError::GenericError("Rejection sampling loop exceeded max iterations, try again with a different signing nonce.")) }
1240
1241 // Alg 7; 11: π² β π
^β β ExpandMask(πβ³, π
)
1242 let mut y = expand_mask::<l, GAMMA1, GAMMA1_MASK_LEN>(&rho_p_p, kappa);
1243
1244 let w = { // scope for y_hat
1245 // Alg 7; 12: π° β NTTβ1(π_hat * NTT(π²))
1246 let mut y_hat = y.clone();
1247 y_hat.ntt();
1248 let mut w = A_hat.matrix_vector_ntt(&y_hat);
1249 w.inv_ntt();
1250 w.conditional_add_q();
1251 w
1252 };
1253
1254 // Alg 7; 13: π°1 β HighBits(π°)
1255 // β· signerβs commitment
1256 let w1 = w.high_bits::<GAMMA2>();
1257
1258 { // scope for h
1259 // 15: π_tilde β H(π||w1Encode(π°1), π/4)
1260 // β· commitment hash
1261 let mut hash = H::new();
1262 hash.absorb(mu);
1263 // hash.absorb(&w1.w1_encode::<W1_PACKED_LEN, POLY_W1_PACKED_LEN>());
1264 w1.w1_encode_and_hash::<W1_PACKED_LEN, POLY_W1_PACKED_LEN>(&mut hash);
1265 hash.squeeze_out(&mut sig_val_c_tilde);
1266 }
1267
1268 // Alg 7; 16: π β π
π β SampleInBall(c_tilde)
1269 // β· verifierβs challenge
1270 let c_hat = { // scope for c
1271 let c = sample_in_ball::<LAMBDA_over_4, TAU>(&sig_val_c_tilde);
1272
1273 // 17: π_hat β NTT(π)
1274 ntt(&c)
1275 };
1276
1277 let t_hat: Vector<k>;
1278 sig_val_z = { // scope for s1_hat, cs1
1279 // Alg 7; 2: π¬1Μ_hat β NTT(π¬1)
1280 let mut s1_hat = s1.clone();
1281 s1_hat.ntt();
1282
1283 y = { // scope for cs1
1284 // Alg 7; 18: β¨β¨ππ¬1β©β© β NTTβ1(π_hat * π¬1_hat)
1285 // Note: <<.>> in FIPS 204 means that this value will be used again later, so you should hang on to it.
1286 let mut cs1 = s1_hat.scalar_vector_ntt(&c_hat);
1287 cs1.inv_ntt();
1288
1289 // Alg 7; 20: π³ β π² + β¨β¨ππ¬1β©β©
1290 y.add_vector_ntt(&cs1);
1291 y
1292 };
1293
1294 // also, while we have s1_hat in memory, compute t_hat
1295 // Alg 6; 5: π β NTTβ1(π β NTT(π¬1)) + π¬2
1296 // β· compute π = ππ¬1 + π¬2
1297 t_hat = A_hat.matrix_vector_ntt(&s1_hat);
1298
1299 y
1300 };
1301
1302 // Alg 7; 23 (first half): if ||π³||β β₯ πΎ1 β π½ or ||π«0||β β₯ πΎ2 β π½ then (z, h) β β₯
1303 // β· validity checks
1304 // out-of-order on purpose for performance reasons:
1305 // might as well do the rejection sampling check before any extra heavy computation
1306 if sig_val_z.check_norm(GAMMA1 - BETA) {
1307 kappa += l as u16;
1308 continue;
1309 };
1310
1311 let t0: Vector<k>;
1312 let mut r0: Vector<k> = { // scope for s2_hat and cs2
1313 // 3: π¬2Μ_hat β NTT(π¬2)
1314 let mut s2_hat = s2.clone();
1315 s2_hat.ntt();
1316
1317 // 19: β¨β¨ππ¬2β©β© β NTTβ1(π_hat * π¬2Μ_hat)
1318 let mut cs2 = s2_hat.scalar_vector_ntt(&c_hat);
1319 cs2.inv_ntt();
1320
1321 // 21: π«0 β LowBits(π° β β¨β¨ππ¬2β©β©)
1322 let r0 = w.sub_vector(&cs2).low_bits::<GAMMA2>();
1323
1324 // while we have s2_hat in scope, derive t0
1325 let mut t = t_hat;
1326 t.inv_ntt();
1327 t.add_vector_ntt(&s2);
1328 t.conditional_add_q();
1329
1330 // 6: (π1, π0) β Power2Round(π)
1331 // β· compress π
1332 // β· PowerTwoRound is applied componentwise (see explanatory text in Section 7.4)
1333 let (_t1tmp, t0tmp) = power_2_round_vec::<k>(&t);
1334 t0 = t0tmp;
1335
1336 r0
1337 };
1338
1339 // Alg 7; 23 (second half): if ||π³||β β₯ πΎ1 β π½ or ||π«0||β β₯ πΎ2 β π½ then (z, h) β β₯
1340 // β· validity checks
1341 if r0.check_norm(GAMMA2 - BETA) {
1342 kappa += l as u16;
1343 continue;
1344 };
1345
1346 let ct0: Vector<k> = { // scope for t0_hat
1347 // 4: π0Μ_hat β NTT(π0)Μ
1348 let mut t0_hat = t0.clone();
1349 t0_hat.ntt();
1350
1351 // 25: β¨β¨ππ0β©β© β NTTβ1(π_hat * π0Μ_hat )
1352 let mut ct0 = t0_hat.scalar_vector_ntt(&c_hat);
1353 ct0.inv_ntt();
1354 ct0
1355 };
1356
1357 // Alg 7; 28 (first half): if ||β¨β¨ππ0β©β©||β β₯ πΎ2 or the number of 1βs in π‘ is greater than π, then (z, h) β β₯
1358 // out-of-order on purpose for performance reasons:
1359 // might as well do the rejection sampling check before any extra heavy computation
1360 if ct0.check_norm(GAMMA2) {
1361 kappa += l as u16;
1362 continue;
1363 };
1364
1365 // Alg 7; 26: π‘ β MakeHint(ββ¨β¨ππ0β©β©, π° β β¨β¨ππ¬2β©β© + β¨β¨ππ0β©β©)
1366 // β· Signerβs hint
1367 r0.add_vector_ntt(&ct0);
1368 r0.conditional_add_q();
1369 let hint_hamming_weight: i32;
1370 sig_val_h = { // scope for hint
1371 let (hint, inner_hint_hamming_weight) =
1372 make_hint_vecs::<k, GAMMA2>(&r0, &w1);
1373 hint_hamming_weight = inner_hint_hamming_weight;
1374 hint
1375 };
1376
1377 // Alg 7; 28 (second half): if ||β¨β¨ππ0β©β©||β β₯ πΎ2 or the number of 1βs in π‘ is greater than π, then (z, h) β β₯
1378 if hint_hamming_weight > OMEGA {
1379 kappa += l as u16;
1380 continue;
1381 };
1382
1383 // "In addition, there is an alternative way of implementing the validity checks on π³ and the computation of
1384 // π‘, which is described in Section 5.1 of [6] (dilithium-specification-round3-20210208.pdf).
1385 // This method may also be used in implementations of ML-DSA."
1386 // todo -- I believe this code is already using this optimization, but it could use a deeper look to see if more optimization is possible.
1387
1388 break;
1389 }
1390
1391 // zeroize rho_p_p before returning it to the OS
1392 rho_p_p.fill(0u8);
1393
1394 // sig_encode does not necessarily write to all bytes of the output, so just to be safe:
1395 output.fill(0u8);
1396
1397 // Alg 7; 33: π β sigEncode(π, π³Μ modΒ±π, π‘)
1398 let bytes_written = sig_encode::<GAMMA1, k, l, LAMBDA_over_4, OMEGA, POLY_Z_PACKED_LEN, SIG_LEN>
1399 (&sig_val_c_tilde, &sig_val_z, &sig_val_h, output);
1400
1401 Ok(bytes_written)
1402 }
1403 /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow.
1404 /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final]
1405 fn set_signer_rnd(&mut self, rnd: [u8; 32]) {
1406 self.signer_rnd = Some(rnd);
1407 }
1408
1409 /// Alternative initialization of the streaming signer where you have your private key
1410 /// as a seed and you want to delay its expansion as late as possible for memory-usage reasons.
1411 fn sign_init_from_seed(seed: &KeyMaterialSized<32>, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1412 let (_pk, sk) = Self::keygen_from_seed(seed)?;
1413 Ok(
1414 Self {
1415 _phantom: PhantomData,
1416 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1417 signer_rnd: None,
1418 sk: None,
1419 seed: Some(seed.clone()),
1420 pk: None }
1421 )
1422 }
1423
1424 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1425 /// Internal function to verify a signature π for a formatted message πβ² .
1426 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1427 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1428 fn verify_mu_internal(
1429 pk: &PK,
1430 mu: &[u8; 64],
1431 sig: &[u8; SIG_LEN],
1432 ) -> bool {
1433 // 1: (π, π1) β pkDecode(ππ)
1434 // Already done -- the pk struct is already decoded
1435
1436 // 2: (π_tilde, π³, π‘) β sigDecode(π)
1437 // β· signerβs commitment hash c_tilde, response π³, and hint π‘
1438 // 3: if π‘ = β₯ then return false
1439 let (c_tilde, z, h) = match sig_decode::<GAMMA1, k, l, LAMBDA_over_4, OMEGA, POLY_Z_PACKED_LEN, SIG_LEN>(&sig) {
1440 Ok((c_tilde, z, h)) => (c_tilde, z, h),
1441 Err(_) => return false,
1442 };
1443
1444 // 13 (first half) return [[ ||π³||β < πΎ1 β π½]]
1445 if z.check_norm(GAMMA1 - BETA) {
1446 return false;
1447 }
1448
1449 // 5: π β ExpandA(π)
1450 // β· π is generated and stored in NTT representation as π
1451 #[allow(non_snake_case)]
1452 let A_hat = expandA::<k, l>(&pk.rho());
1453
1454 // 6: π‘π β H(ππ, 64)
1455 // 7: π β (H(BytesToBits(π‘π)||π β², 64))
1456 // β· message representative that may optionally be
1457 // computed in a different cryptographic module
1458 // skip because this function is being handed mu
1459
1460 // 8: π β π
π β SampleInBall(c_tilde)
1461 let c = sample_in_ball::<LAMBDA_over_4, TAU>(&c_tilde);
1462
1463
1464 // 9: π°β²_approx β NTTβ1(π_hat β NTT(π³) β NTT(π) β NTT(π1 β
2^π))
1465 // broken out for clarity:
1466 // NTTβ1(
1467 // π_hat β NTT(π³) β
1468 // NTT(π) β NTT(π1 β
2^π)
1469 // )
1470 // β· π°'_approx = ππ³ β ππ1 β
2^π
1471 // weird nested scoping is to reduce peak stack memory usage
1472 let w1p = {
1473 let w1 = {
1474 let mut z_hat = z.clone();
1475 z_hat.ntt();
1476 A_hat.matrix_vector_ntt(&z_hat)
1477 };
1478 let w2 = {
1479 let mut t1_shift_hat = pk.t1().shift_left::<d>();
1480 t1_shift_hat.ntt();
1481 t1_shift_hat.scalar_vector_ntt(&ntt(&c))
1482 };
1483 let mut wp_approx = w1.sub_vector(&w2);
1484 wp_approx.inv_ntt();
1485 wp_approx.conditional_add_q();
1486 // bc-java does a wp_approx.conditional_add_q();
1487
1488 // 10: π°1β² β UseHint(π‘, π°'_approx)
1489 // β· reconstruction of signerβs commitment
1490 use_hint_vecs::<k, GAMMA2>(&h, &wp_approx)
1491 };
1492 // 12: π_tilde_p β H(π||w1Encode(π°1'), π/4)
1493 // β· hash it; this should match π_tilde
1494 let mut c_tilde_p = [0u8; LAMBDA_over_4];
1495 let mut hash = H::new();
1496 hash.absorb(mu);
1497 w1p.w1_encode_and_hash::<W1_PACKED_LEN, POLY_W1_PACKED_LEN>(&mut hash);
1498 hash.squeeze_out(&mut c_tilde_p);
1499
1500
1501 // verification probably doesn't technically need to be constant-time, but why not?
1502 // 13 (second half): return [[ ||π³||β < πΎ1 β π½]] and [[π Μ = πβ² ]]
1503 bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p)
1504 }
1505}
1506
1507/// Trait for all three of the ML-DSA algorithm variants.
1508pub trait MLDSATrait<
1509 const PK_LEN: usize,
1510 const SK_LEN: usize,
1511 const SIG_LEN: usize,
1512 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
1513 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
1514 const k: usize,
1515 const l: usize,
1516 const ETA: usize
1517> : Sized {
1518 /// Imports a secret key from a seed.
1519 fn keygen_from_seed(seed: &KeyMaterialSized<32>) -> Result<(PK, SK), SignatureError>;
1520 /// Imports a secret key from both a seed and an encoded_sk.
1521 ///
1522 /// This is a convenience function to expand the key from seed and compare it against
1523 /// the provided `encoded_sk` using a constant-time equality check.
1524 /// If everything checks out, the secret key is returned fully populated with pk and seed.
1525 /// If the provided key and derived key don't match, an error is returned.
1526 fn keygen_from_seed_and_encoded(
1527 seed: &KeyMaterialSized<32>,
1528 encoded_sk: &[u8; SK_LEN],
1529 ) -> Result<
1530 (PK, SK),
1531 SignatureError,
1532 >;
1533 /// Given a public key and a secret key, check that the public key matches the secret key.
1534 /// This is a sanity check that the public key was generated correctly from the secret key.
1535 ///
1536 /// At the current time, this is only possible if `sk` either contains a public key (in which case
1537 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
1538 /// (in which case a keygen_from_seed is run and then the pk's compared).
1539 ///
1540 /// Returns either `()` or [SignatureError::ConsistencyCheckFailed].
1541 fn keypair_consistency_check(
1542 pk: &PK,
1543 sk: &SK,
1544 ) -> Result<(), SignatureError>;
1545 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
1546 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
1547 ///
1548 /// This function, together with [MLDSATrait::sign_mu] perform a complete ML-DSA signature which is indistinguishable
1549 /// from one produced by the one-shot sign APIs.
1550 ///
1551 /// The utility of this function is exactly as described
1552 /// on Line 6 of Algorithm 7 of FIPS 204:
1553 ///
1554 /// message representative that may optionally be computed in a different cryptographic module
1555 ///
1556 /// The utility is when an extremely large message needs to be signed, where the message exists on one
1557 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
1558 /// causes operational concerns (this is common for example with network HSMs or sending large messages
1559 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
1560 /// contents of the message are sensitive and the signer does not want to transmit the message itself
1561 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
1562 ///
1563 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
1564 /// can be used with many hardware crypto modules.
1565 ///
1566 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
1567 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
1568 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
1569 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
1570 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
1571 /// ML-DSA verifier.
1572 ///
1573 /// This function requires the public key hash `tr`, which can be computed from the public key
1574 /// using [MLDSAPublicKeyTrait::compute_tr].
1575 ///
1576 /// For a streaming version of this, see [MuBuilder].
1577 fn compute_mu_from_tr(
1578 msg: &[u8],
1579 ctx: Option<&[u8]>,
1580 tr: &[u8; 64],
1581 ) -> Result<[u8; 64], SignatureError>;
1582 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the public key.
1583 fn compute_mu_from_pk(
1584 msg: &[u8],
1585 ctx: Option<&[u8]>,
1586 pk: &PK,
1587 ) -> Result<[u8; 64], SignatureError>;
1588 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the private key.
1589 fn compute_mu_from_sk(
1590 msg: &[u8],
1591 ctx: Option<&[u8]>,
1592 sk: &SK,
1593 ) -> Result<[u8; 64], SignatureError>;
1594 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1595 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1596 /// FIPS 204 itself, as well as subsequent FAQ documents.
1597 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1598 fn sign_mu(
1599 sk: &SK,
1600 mu: &[u8; 64],
1601 ) -> Result<[u8; SIG_LEN], SignatureError>;
1602 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1603 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1604 /// FIPS 204 itself, as well as subsequent FAQ documents.
1605 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1606 ///
1607 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1608 fn sign_mu_out(
1609 sk: &SK,
1610 mu: &[u8; 64],
1611 output: &mut [u8; SIG_LEN],
1612 ) -> Result<usize, SignatureError>;
1613 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1614 /// (modified to take an externally-computed mu instead of M')
1615 ///
1616 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1617 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1618 /// FIPS 204 itself, as well as subsequent FAQ documents.
1619 ///
1620 /// Security note:
1621 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1622 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1623 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
1624 /// If not, you may lose some privacy properties; for example it becomes easy to tell if a signer
1625 /// has signed the same message twice or two different messagase, or to tell if the same message
1626 /// has been signed by the same signer twice or two different signers.
1627 ///
1628 /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help
1629 /// prevent accidental nonce reuse, this function moves `rnd`.
1630 fn sign_mu_deterministic(
1631 sk: &SK,
1632 mu: &[u8; 64],
1633 rnd: [u8; 32],
1634 ) -> Result<[u8; SIG_LEN], SignatureError>;
1635 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1636 /// (modified to take an externally-computed mu instead of M')
1637 ///
1638 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1639 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1640 /// FIPS 204 itself, as well as subsequent FAQ documents.
1641 /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG.
1642 ///
1643 /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help
1644 /// prevent accidental nonce reuse, this function moves `rnd`.
1645 ///
1646 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1647 fn sign_mu_deterministic_out(
1648 sk: &SK,
1649 mu: &[u8; 64],
1650 rnd: [u8; 32],
1651 output: &mut [u8; SIG_LEN],
1652 ) -> Result<usize, SignatureError>;
1653 /// This contains a heavily-optimized combined keygen() and sign() which aims to reduce peak
1654 /// memory usage by never having the full secret key in memory at the same time,
1655 /// and by deriving intermediate values piece-wise as needed.
1656 fn sign_mu_deterministic_from_seed(
1657 seed: &KeyMaterialSized<32>,
1658 mu: &[u8; 64],
1659 rnd: [u8; 32],
1660 ) -> Result<[u8; SIG_LEN], SignatureError>;
1661 /// This contains a heavily-optimized combined keygen() and sign() which aims to reduce peak
1662 /// memory usage by never having the full secret key in memory at the same time,
1663 /// and by deriving intermediate values piece-wise as needed.
1664 fn sign_mu_deterministic_from_seed_out(
1665 seed: &KeyMaterialSized<32>,
1666 mu: &[u8; 64],
1667 rnd: [u8; 32],
1668 output: &mut [u8; SIG_LEN],
1669 ) -> Result<usize, SignatureError>;
1670 /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow.
1671 /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final]
1672 fn set_signer_rnd(&mut self, rnd: [u8; 32]);
1673 /// An alternate way to start the streaming signing mode by providing a private key seed instead of an expanded private key
1674 fn sign_init_from_seed(seed: &KeyMaterialSized<32>, ctx: Option<&[u8]>) -> Result<Self, SignatureError>;
1675 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1676 /// Internal function to verify a signature π for a formatted message πβ² .
1677 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1678 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1679 fn verify_mu_internal(
1680 pk: &PK,
1681 mu: &[u8; 64],
1682 sig: &[u8; SIG_LEN],
1683 ) -> bool;
1684}
1685
1686impl<
1687 const PK_LEN: usize,
1688 const SK_LEN: usize,
1689 const SIG_LEN: usize,
1690 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
1691 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
1692 const TAU: i32,
1693 const LAMBDA: i32,
1694 const GAMMA1: i32,
1695 const GAMMA2: i32,
1696 const k: usize,
1697 const l: usize,
1698 const ETA: usize,
1699 const BETA: i32,
1700 const OMEGA: i32,
1701 const C_TILDE: usize,
1702 const POLY_Z_PACKED_LEN: usize,
1703 const POLY_W1_PACKED_LEN: usize,
1704 const W1_PACKED_LEN: usize,
1705 const POLY_ETA_PACKED_LEN: usize,
1706 const LAMBDA_over_4: usize,
1707 const GAMMA1_MASK_LEN: usize,
1708> Signature<PK, SK> for MLDSA<
1709 PK_LEN,
1710 SK_LEN,
1711 SIG_LEN,
1712 PK,
1713 SK,
1714 TAU,
1715 LAMBDA,
1716 GAMMA1,
1717 GAMMA2,
1718 k,
1719 l,
1720 ETA,
1721 BETA,
1722 OMEGA,
1723 C_TILDE,
1724 POLY_Z_PACKED_LEN,
1725 POLY_W1_PACKED_LEN,
1726 W1_PACKED_LEN,
1727 POLY_ETA_PACKED_LEN,
1728 LAMBDA_over_4,
1729 GAMMA1_MASK_LEN,
1730> {
1731
1732 fn keygen() -> Result<(PK, SK), SignatureError> {
1733 Self::keygen_from_os_rng()
1734 }
1735
1736 fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<Vec<u8>, SignatureError> {
1737 let mut out = vec![0u8; SIG_LEN];
1738 Self::sign_out(sk, msg, ctx, &mut out)?;
1739
1740 Ok(out)
1741 }
1742
1743 fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8]) -> Result<usize, SignatureError> {
1744 let mu = MuBuilder::compute_mu(msg, ctx, &sk.tr())?;
1745 if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) }
1746 let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap();
1747 let bytes_written = Self::sign_mu_out(sk, &mu, output_sized)?;
1748
1749 Ok(bytes_written)
1750 }
1751
1752 fn sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1753 Ok(
1754 Self {
1755 _phantom: PhantomData,
1756 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1757 signer_rnd: None,
1758 sk: Some(sk.clone()),
1759 seed: None,
1760 pk: None }
1761 )
1762 }
1763
1764 fn sign_update(&mut self, msg_chunk: &[u8]) {
1765 self.mu_builder.do_update(msg_chunk);
1766 }
1767
1768 fn sign_final(self) -> Result<Vec<u8>, SignatureError> {
1769 let mut out = [0u8; SIG_LEN];
1770 self.sign_final_out(&mut out)?;
1771 Ok(Vec::from(out))
1772 }
1773
1774 fn sign_final_out(self, output: &mut [u8]) -> Result<usize, SignatureError> {
1775 let mu = self.mu_builder.do_final();
1776
1777 if self.sk.is_none() && self.seed.is_none() {
1778 return Err(SignatureError::GenericError("Somehow you managed to construct a streaming signer without a private key, impressive!"))
1779 }
1780
1781 if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) }
1782 let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap();
1783
1784 if self.sk.is_some() {
1785 if self.signer_rnd.is_none() {
1786 Self::sign_mu_out(&self.sk.unwrap(), &mu, output_sized)
1787 } else {
1788 Self::sign_mu_deterministic_out(&self.sk.unwrap(), &mu, self.signer_rnd.unwrap(), output_sized)
1789 }
1790 } else if self.seed.is_some() {
1791 let rnd = if self.signer_rnd.is_some() {
1792 self.signer_rnd.unwrap()
1793 } else {
1794 let mut rnd: [u8; RND_LEN] = [0u8; RND_LEN];
1795 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
1796 rnd
1797 };
1798 Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output_sized)
1799 } else { unreachable!() }
1800 }
1801
1802 fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> {
1803 let mu = MuBuilder::compute_mu(msg, ctx, &pk.compute_tr())?;
1804
1805 if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1806 if Self::verify_mu_internal(pk, &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1807 Ok(())
1808 } else {
1809 Err(SignatureError::SignatureVerificationFailed)
1810 }
1811 }
1812
1813 fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1814 Ok(
1815 Self {
1816 _phantom: Default::default(),
1817 mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?,
1818 signer_rnd: None,
1819 sk: None,
1820 seed: None,
1821 pk: Some(pk.clone()) }
1822 )
1823 }
1824
1825 fn verify_update(&mut self, msg_chunk: &[u8]) {
1826 self.mu_builder.do_update(msg_chunk);
1827 }
1828
1829 fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> {
1830 let mu = self.mu_builder.do_final();
1831
1832 assert!(self.pk.is_some(), "Somehow you managed to construct a streaming verifier without a public key, impressive!");
1833
1834 if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1835 if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1836 Ok(())
1837 } else {
1838 Err(SignatureError::SignatureVerificationFailed)
1839 }
1840 }
1841}
1842
1843
1844/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204.
1845/// Provides a stateful version of [MLDSATrait::compute_mu_from_pk] and [MLDSATrait::compute_mu_from_tr]
1846/// that supports streaming
1847/// large to-be-signed messages.
1848///
1849/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA
1850/// does not benefit from allowing external construction of the message representative mu.
1851/// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function
1852/// and providing that to HashMLDSA via [PHSignature::sign_ph].
1853pub struct MuBuilder {
1854 h: H,
1855}
1856
1857impl MuBuilder {
1858 /// Algorithm 7
1859 /// 6: π β H(BytesToBits(π‘π)||πβ², 64)
1860 pub fn compute_mu(msg: &[u8], ctx: Option<&[u8]>, tr: &[u8; 64]) -> Result<[u8; 64], SignatureError> {
1861 let mut mu_builder = MuBuilder::do_init(&tr, ctx)?;
1862 mu_builder.do_update(msg);
1863 let mu = mu_builder.do_final();
1864
1865 Ok(mu)
1866 }
1867
1868 /// This function requires the public key hash `tr`, which can be computed from the public key
1869 /// using [MLDSAPublicKeyTrait::compute_tr].
1870 pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1871 let ctx = match ctx { Some(ctx) => ctx, None => &[] };
1872
1873 // Algorithm 2
1874 // 1: if |ππ‘π₯| > 255 then
1875 if ctx.len() > 255 {
1876 return Err(SignatureError::LengthError("ctx value is longer than 255 bytes"));
1877 }
1878
1879 // Algorithm 7
1880 // 6: π β H(BytesToBits(π‘π)||π', 64)
1881 let mut mb = Self { h: H::new() };
1882 mb.h.absorb(tr);
1883
1884 // Algorithm 2
1885 // 10: πβ² β BytesToBits(IntegerToBytes(0, 1) β₯ IntegerToBytes(|ππ‘π₯|, 1) β₯ ππ‘π₯) β₯ π
1886 // all done together
1887 mb.h.absorb(&[0u8]);
1888 mb.h.absorb(&[ctx.len() as u8]);
1889 mb.h.absorb(ctx);
1890
1891 // now ready to absorb M
1892 Ok(mb)
1893 }
1894
1895 /// Stream a chunk of the message.
1896 pub fn do_update(&mut self, msg_chunk: &[u8]) {
1897 self.h.absorb(msg_chunk);
1898 }
1899
1900 /// Finalize and return the mu value.
1901 pub fn do_final(mut self) -> [u8; 64] {
1902 // Completion of
1903 // Algorithm 7
1904 // 6: π β H(BytesToBits(π‘π)||π β², 64)
1905 let mut mu = [0u8; 64];
1906 self.h.squeeze_out(&mut mu);
1907
1908 mu
1909 }
1910}