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 tr: &[u8; 64],
878 msg: &[u8],
879 ctx: Option<&[u8]>,
880 ) -> Result<[u8; 64], SignatureError> {
881 MuBuilder::compute_mu(tr, msg, ctx)
882 }
883 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the public key.
884 fn compute_mu_from_pk(
885 pk: &PK,
886 msg: &[u8],
887 ctx: Option<&[u8]>,
888 ) -> Result<[u8; 64], SignatureError> {
889 MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)
890 }
891 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the private key.
892 fn compute_mu_from_sk(
893 sk: &SK,
894 msg: &[u8],
895 ctx: Option<&[u8]>,
896 ) -> Result<[u8; 64], SignatureError> {
897 MuBuilder::compute_mu(&sk.tr(), msg, ctx)
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 Az = {
1474 let mut z_hat = z.clone();
1475 z_hat.ntt();
1476 A_hat.matrix_vector_ntt(&z_hat)
1477 };
1478 let ct1 = {
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 = Az.sub_vector(&ct1);
1484 wp_approx.inv_ntt();
1485 wp_approx.conditional_add_q();
1486
1487 // 10: π°1β² β UseHint(π‘, π°'_approx)
1488 // β· reconstruction of signerβs commitment
1489 use_hint_vecs::<k, GAMMA2>(&h, &wp_approx)
1490 };
1491 // 12: π_tilde_p β H(π||w1Encode(π°1'), π/4)
1492 // β· hash it; this should match π_tilde
1493 let mut c_tilde_p = [0u8; LAMBDA_over_4];
1494 let mut hash = H::new();
1495 hash.absorb(mu);
1496 w1p.w1_encode_and_hash::<W1_PACKED_LEN, POLY_W1_PACKED_LEN>(&mut hash);
1497 hash.squeeze_out(&mut c_tilde_p);
1498
1499
1500 // verification probably doesn't technically need to be constant-time, but why not?
1501 // 13 (second half): return [[ ||π³||β < πΎ1 β π½]] and [[π Μ = πβ² ]]
1502 bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p)
1503 }
1504}
1505
1506/// Trait for all three of the ML-DSA algorithm variants.
1507pub trait MLDSATrait<
1508 const PK_LEN: usize,
1509 const SK_LEN: usize,
1510 const SIG_LEN: usize,
1511 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
1512 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
1513 const k: usize,
1514 const l: usize,
1515 const ETA: usize
1516> : Sized {
1517 /// Imports a secret key from a seed.
1518 fn keygen_from_seed(seed: &KeyMaterialSized<32>) -> Result<(PK, SK), SignatureError>;
1519 /// Imports a secret key from both a seed and an encoded_sk.
1520 ///
1521 /// This is a convenience function to expand the key from seed and compare it against
1522 /// the provided `encoded_sk` using a constant-time equality check.
1523 /// If everything checks out, the secret key is returned fully populated with pk and seed.
1524 /// If the provided key and derived key don't match, an error is returned.
1525 fn keygen_from_seed_and_encoded(
1526 seed: &KeyMaterialSized<32>,
1527 encoded_sk: &[u8; SK_LEN],
1528 ) -> Result<
1529 (PK, SK),
1530 SignatureError,
1531 >;
1532 /// Given a public key and a secret key, check that the public key matches the secret key.
1533 /// This is a sanity check that the public key was generated correctly from the secret key.
1534 ///
1535 /// At the current time, this is only possible if `sk` either contains a public key (in which case
1536 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
1537 /// (in which case a keygen_from_seed is run and then the pk's compared).
1538 ///
1539 /// Returns either `()` or [SignatureError::ConsistencyCheckFailed].
1540 fn keypair_consistency_check(
1541 pk: &PK,
1542 sk: &SK,
1543 ) -> Result<(), SignatureError>;
1544 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
1545 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
1546 ///
1547 /// This function, together with [MLDSATrait::sign_mu] perform a complete ML-DSA signature which is indistinguishable
1548 /// from one produced by the one-shot sign APIs.
1549 ///
1550 /// The utility of this function is exactly as described
1551 /// on Line 6 of Algorithm 7 of FIPS 204:
1552 ///
1553 /// message representative that may optionally be computed in a different cryptographic module
1554 ///
1555 /// The utility is when an extremely large message needs to be signed, where the message exists on one
1556 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
1557 /// causes operational concerns (this is common for example with network HSMs or sending large messages
1558 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
1559 /// contents of the message are sensitive and the signer does not want to transmit the message itself
1560 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
1561 ///
1562 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
1563 /// can be used with many hardware crypto modules.
1564 ///
1565 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
1566 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
1567 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
1568 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
1569 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
1570 /// ML-DSA verifier.
1571 ///
1572 /// This function requires the public key hash `tr`, which can be computed from the public key
1573 /// using [MLDSAPublicKeyTrait::compute_tr].
1574 ///
1575 /// For a streaming version of this, see [MuBuilder].
1576 fn compute_mu_from_tr(
1577 tr: &[u8; 64],
1578 msg: &[u8],
1579 ctx: Option<&[u8]>,
1580 ) -> Result<[u8; 64], SignatureError>;
1581 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the public key.
1582 fn compute_mu_from_pk(
1583 pk: &PK,
1584 msg: &[u8],
1585 ctx: Option<&[u8]>,
1586 ) -> Result<[u8; 64], SignatureError>;
1587 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the private key.
1588 fn compute_mu_from_sk(
1589 sk: &SK,
1590 msg: &[u8],
1591 ctx: Option<&[u8]>,
1592 ) -> Result<[u8; 64], SignatureError>;
1593 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1594 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1595 /// FIPS 204 itself, as well as subsequent FAQ documents.
1596 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1597 fn sign_mu(
1598 sk: &SK,
1599 mu: &[u8; 64],
1600 ) -> Result<[u8; SIG_LEN], SignatureError>;
1601 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1602 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1603 /// FIPS 204 itself, as well as subsequent FAQ documents.
1604 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1605 ///
1606 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1607 fn sign_mu_out(
1608 sk: &SK,
1609 mu: &[u8; 64],
1610 output: &mut [u8; SIG_LEN],
1611 ) -> Result<usize, SignatureError>;
1612 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1613 /// (modified to take an externally-computed mu instead of M')
1614 ///
1615 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1616 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1617 /// FIPS 204 itself, as well as subsequent FAQ documents.
1618 ///
1619 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1620 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1621 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1622 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1623 /// of taking it by reference.
1624 ///
1625 /// Security note about deterministic mode:
1626 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1627 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1628 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
1629 /// If not, you may lose some privacy properties; for example, it becomes easy to tell if a signer
1630 /// has signed the same message twice or two different messages, or to tell if the same message
1631 /// has been signed by the same signer twice or two different signers.
1632 fn sign_mu_deterministic(
1633 sk: &SK,
1634 mu: &[u8; 64],
1635 rnd: [u8; 32],
1636 ) -> Result<[u8; SIG_LEN], SignatureError>;
1637 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1638 /// (modified to take an externally-computed mu instead of M')
1639 ///
1640 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1641 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1642 /// FIPS 204 itself, as well as subsequent FAQ documents.
1643 /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG.
1644 ///
1645 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1646 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1647 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1648 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1649 /// of taking it by reference.
1650 ///
1651 /// Security note about deterministic mode:
1652 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1653 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1654 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
1655 /// If not, you may lose some privacy properties; for example, it becomes easy to tell if a signer
1656 /// has signed the same message twice or two different messages, or to tell if the same message
1657 /// has been signed by the same signer twice or two different signers.
1658 ///
1659 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1660 fn sign_mu_deterministic_out(
1661 sk: &SK,
1662 mu: &[u8; 64],
1663 rnd: [u8; 32],
1664 output: &mut [u8; SIG_LEN],
1665 ) -> Result<usize, SignatureError>;
1666 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1667 /// memory usage by never having the full secret key in memory at the same time,
1668 /// and by deriving intermediate values piece-wise as needed.
1669 fn sign_mu_deterministic_from_seed(
1670 seed: &KeyMaterialSized<32>,
1671 mu: &[u8; 64],
1672 rnd: [u8; 32],
1673 ) -> Result<[u8; SIG_LEN], SignatureError>;
1674 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1675 /// memory usage by never having the full secret key in memory at the same time,
1676 /// and by deriving intermediate values piece-wise as needed.
1677 fn sign_mu_deterministic_from_seed_out(
1678 seed: &KeyMaterialSized<32>,
1679 mu: &[u8; 64],
1680 rnd: [u8; 32],
1681 output: &mut [u8; SIG_LEN],
1682 ) -> Result<usize, SignatureError>;
1683 /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow.
1684 /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final]
1685 fn set_signer_rnd(&mut self, rnd: [u8; 32]);
1686 /// An alternate way to start the streaming signing mode by providing a private key seed instead of an expanded private key
1687 fn sign_init_from_seed(seed: &KeyMaterialSized<32>, ctx: Option<&[u8]>) -> Result<Self, SignatureError>;
1688 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1689 /// Internal function to verify a signature π for a formatted message πβ² .
1690 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1691 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1692 fn verify_mu_internal(
1693 pk: &PK,
1694 mu: &[u8; 64],
1695 sig: &[u8; SIG_LEN],
1696 ) -> bool;
1697}
1698
1699impl<
1700 const PK_LEN: usize,
1701 const SK_LEN: usize,
1702 const SIG_LEN: usize,
1703 PK: MLDSAPublicKeyTrait<k, PK_LEN> + MLDSAPublicKeyInternalTrait<k, PK_LEN>,
1704 SK: MLDSAPrivateKeyTrait<k, l, ETA, SK_LEN, PK_LEN> + MLDSAPrivateKeyInternalTrait<k, l, ETA, SK_LEN, PK_LEN>,
1705 const TAU: i32,
1706 const LAMBDA: i32,
1707 const GAMMA1: i32,
1708 const GAMMA2: i32,
1709 const k: usize,
1710 const l: usize,
1711 const ETA: usize,
1712 const BETA: i32,
1713 const OMEGA: i32,
1714 const C_TILDE: usize,
1715 const POLY_Z_PACKED_LEN: usize,
1716 const POLY_W1_PACKED_LEN: usize,
1717 const W1_PACKED_LEN: usize,
1718 const POLY_ETA_PACKED_LEN: usize,
1719 const LAMBDA_over_4: usize,
1720 const GAMMA1_MASK_LEN: usize,
1721> Signature<PK, SK> for MLDSA<
1722 PK_LEN,
1723 SK_LEN,
1724 SIG_LEN,
1725 PK,
1726 SK,
1727 TAU,
1728 LAMBDA,
1729 GAMMA1,
1730 GAMMA2,
1731 k,
1732 l,
1733 ETA,
1734 BETA,
1735 OMEGA,
1736 C_TILDE,
1737 POLY_Z_PACKED_LEN,
1738 POLY_W1_PACKED_LEN,
1739 W1_PACKED_LEN,
1740 POLY_ETA_PACKED_LEN,
1741 LAMBDA_over_4,
1742 GAMMA1_MASK_LEN,
1743> {
1744
1745 fn keygen() -> Result<(PK, SK), SignatureError> {
1746 Self::keygen_from_os_rng()
1747 }
1748
1749 fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<Vec<u8>, SignatureError> {
1750 let mut out = vec![0u8; SIG_LEN];
1751 Self::sign_out(sk, msg, ctx, &mut out)?;
1752
1753 Ok(out)
1754 }
1755
1756 fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8]) -> Result<usize, SignatureError> {
1757 let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?;
1758 if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) }
1759 let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap();
1760 let bytes_written = Self::sign_mu_out(sk, &mu, output_sized)?;
1761
1762 Ok(bytes_written)
1763 }
1764
1765 fn sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1766 Ok(
1767 Self {
1768 _phantom: PhantomData,
1769 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1770 signer_rnd: None,
1771 sk: Some(sk.clone()),
1772 seed: None,
1773 pk: None }
1774 )
1775 }
1776
1777 fn sign_update(&mut self, msg_chunk: &[u8]) {
1778 self.mu_builder.do_update(msg_chunk);
1779 }
1780
1781 fn sign_final(self) -> Result<Vec<u8>, SignatureError> {
1782 let mut out = [0u8; SIG_LEN];
1783 self.sign_final_out(&mut out)?;
1784 Ok(Vec::from(out))
1785 }
1786
1787 fn sign_final_out(self, output: &mut [u8]) -> Result<usize, SignatureError> {
1788 let mu = self.mu_builder.do_final();
1789
1790 if self.sk.is_none() && self.seed.is_none() {
1791 return Err(SignatureError::GenericError("Somehow you managed to construct a streaming signer without a private key, impressive!"))
1792 }
1793
1794 if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) }
1795 let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap();
1796
1797 if self.sk.is_some() {
1798 if self.signer_rnd.is_none() {
1799 Self::sign_mu_out(&self.sk.unwrap(), &mu, output_sized)
1800 } else {
1801 Self::sign_mu_deterministic_out(&self.sk.unwrap(), &mu, self.signer_rnd.unwrap(), output_sized)
1802 }
1803 } else if self.seed.is_some() {
1804 let rnd = if self.signer_rnd.is_some() {
1805 self.signer_rnd.unwrap()
1806 } else {
1807 let mut rnd: [u8; RND_LEN] = [0u8; RND_LEN];
1808 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
1809 rnd
1810 };
1811 Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output_sized)
1812 } else { unreachable!() }
1813 }
1814
1815 fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> {
1816 let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?;
1817
1818 if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1819 if Self::verify_mu_internal(pk, &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1820 Ok(())
1821 } else {
1822 Err(SignatureError::SignatureVerificationFailed)
1823 }
1824 }
1825
1826 fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1827 Ok(
1828 Self {
1829 _phantom: Default::default(),
1830 mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?,
1831 signer_rnd: None,
1832 sk: None,
1833 seed: None,
1834 pk: Some(pk.clone()) }
1835 )
1836 }
1837
1838 fn verify_update(&mut self, msg_chunk: &[u8]) {
1839 self.mu_builder.do_update(msg_chunk);
1840 }
1841
1842 fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> {
1843 let mu = self.mu_builder.do_final();
1844
1845 assert!(self.pk.is_some(), "Somehow you managed to construct a streaming verifier without a public key, impressive!");
1846
1847 if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1848 if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1849 Ok(())
1850 } else {
1851 Err(SignatureError::SignatureVerificationFailed)
1852 }
1853 }
1854}
1855
1856
1857/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204.
1858/// Provides a stateful version of [MLDSATrait::compute_mu_from_pk] and [MLDSATrait::compute_mu_from_tr]
1859/// that supports streaming
1860/// large to-be-signed messages.
1861///
1862/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA
1863/// does not benefit from allowing external construction of the message representative mu.
1864/// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function
1865/// and providing that to HashMLDSA via [PHSignature::sign_ph].
1866pub struct MuBuilder {
1867 h: H,
1868}
1869
1870impl MuBuilder {
1871 /// Algorithm 7
1872 /// 6: π β H(BytesToBits(π‘π)||πβ², 64)
1873 pub fn compute_mu(tr: &[u8; 64],msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; 64], SignatureError> {
1874 let mut mu_builder = MuBuilder::do_init(&tr, ctx)?;
1875 mu_builder.do_update(msg);
1876 let mu = mu_builder.do_final();
1877
1878 Ok(mu)
1879 }
1880
1881 /// This function requires the public key hash `tr`, which can be computed from the public key
1882 /// using [MLDSAPublicKeyTrait::compute_tr].
1883 pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1884 let ctx = match ctx { Some(ctx) => ctx, None => &[] };
1885
1886 // Algorithm 2
1887 // 1: if |ππ‘π₯| > 255 then
1888 if ctx.len() > 255 {
1889 return Err(SignatureError::LengthError("ctx value is longer than 255 bytes"));
1890 }
1891
1892 // Algorithm 7
1893 // 6: π β H(BytesToBits(π‘π)||π', 64)
1894 let mut mb = Self { h: H::new() };
1895 mb.h.absorb(tr);
1896
1897 // Algorithm 2
1898 // 10: πβ² β BytesToBits(IntegerToBytes(0, 1) β₯ IntegerToBytes(|ππ‘π₯|, 1) β₯ ππ‘π₯) β₯ π
1899 // all done together
1900 mb.h.absorb(&[0u8]);
1901 mb.h.absorb(&[ctx.len() as u8]);
1902 mb.h.absorb(ctx);
1903
1904 // now ready to absorb M
1905 Ok(mb)
1906 }
1907
1908 /// Stream a chunk of the message.
1909 pub fn do_update(&mut self, msg_chunk: &[u8]) {
1910 self.h.absorb(msg_chunk);
1911 }
1912
1913 /// Finalize and return the mu value.
1914 pub fn do_final(mut self) -> [u8; 64] {
1915 // Completion of
1916 // Algorithm 7
1917 // 6: π β H(BytesToBits(π‘π)||π β², 64)
1918 let mut mu = [0u8; 64];
1919 self.h.squeeze_out(&mut mu);
1920
1921 mu
1922 }
1923}