bouncycastle_mldsa_lowmemory/mldsa.rs
1//! This page documents advanced features of the Module Lattice Digital Signature Algorithm (ML-DSA)
2//! available in this crate.
3//!
4//!
5//! # Streaming APIs
6//!
7//! Sometimes the message 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::errors::SignatureError;
12//! use bouncycastle_core::traits::Signature;
13//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
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 = 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::errors::SignatureError;
54//! use bouncycastle_core::traits::Signature;
55//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
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 = 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::errors::SignatureError;
100//! use bouncycastle_core::traits::Signature;
101//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
102//!
103//! let (pk, _) = MLDSA65::keygen().unwrap();
104//!
105//! // Let's pretend this message was so long that 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(&pk.compute_tr(), msg, None).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::errors::SignatureError;
119//! use bouncycastle_core::traits::Signature;
120//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
121//!
122//! let (pk, _) = MLDSA65::keygen().unwrap();
123//!
124//! // Let's pretend this message was so long that 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::errors::SignatureError;
139//! use bouncycastle_core::traits::Signature;
140//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
141//!
142//! let msg = b"The quick brown fox jumped over the lazy dog";
143//!
144//! let (pk, sk) = MLDSA65::keygen().unwrap();
145//!
146//! // Assume this was computed somewhere else and sent to you.
147//! // They would have had to know pk!
148//! let mu: [u8; 64] = MuBuilder::compute_mu(&pk.compute_tr(), msg, None).unwrap();
149//!
150//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap();
151//! // This is the signature value that 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::errors::SignatureError;
184//! use bouncycastle_core::traits::Signature;
185//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait};
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 = 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::errors::SignatureError;
223//! use bouncycastle_core::traits::Signature;
224//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
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(&pk.compute_tr(), msg, None).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::errors::SignatureError;
268//! use bouncycastle_core::traits::Signature;
269//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait};
270//! use bouncycastle_hex as hex;
271//! use bouncycastle_mldsa_lowmemory::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder};
272//!
273//! let msg = b"The quick brown fox jumped over the lazy dog";
274//!
275//! let seed = KeyMaterial256::from_bytes_as_type(
276//! &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(),
277//! KeyType::Seed,
278//! ).unwrap();
279//!
280//! // 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(&tr, msg, None).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 core::marker::PhantomData;
313use crate::aux_functions::{sample_in_ball, bitpack_gamma1, bitlen_eta, unpack_h_row, unpack_c_tilde};
314use crate::low_memory_helpers::{compute_ct0_component, compute_w0cs2_component, compute_w_row, compute_wp_approx_row, compute_z_component, s_unpack};
315use crate::mldsa_keys::{MLDSAPublicKeyTrait, MLDSAPublicKeyInternalTrait};
316use crate::mldsa_keys::{MLDSAPrivateKeyTrait, MLDSAPrivateKeyInternalTrait};
317use crate::{MLDSA44PublicKey, MLDSA44PrivateKey, MLDSA65PublicKey, MLDSA65PrivateKey, MLDSA87PublicKey, MLDSA87PrivateKey};
318use bouncycastle_core::errors::SignatureError;
319use bouncycastle_core::key_material::{KeyMaterial};
320use bouncycastle_core::traits::{RNG, SecurityStrength, XOF, Signature, Algorithm};
321use bouncycastle_rng::{HashDRBG_SHA512};
322use bouncycastle_sha3::{SHAKE128, SHAKE256};
323
324
325// imports needed just for docs
326#[allow(unused_imports)]
327use bouncycastle_core::traits::PHSignature;
328#[allow(unused_imports)]
329use bouncycastle_core::key_material::KeyMaterial256;
330#[allow(unused_imports)]
331use crate::hash_mldsa;
332/*** Constants ***/
333
334///
335pub const ML_DSA_44_NAME: &str = "ML-DSA-44";
336///
337pub const ML_DSA_65_NAME: &str = "ML-DSA-65";
338///
339pub const ML_DSA_87_NAME: &str = "ML-DSA-87";
340
341// From FIPS 204 Table 1 and Table 2
342
343// Constants that are the same for all parameter sets
344pub(crate) const N: usize = 256;
345pub(crate) const q: i32 = 8380417;
346pub(crate) const q_inv: i32 = 58728449; // q ^ (-1) mod 2 ^32
347pub(crate) const d: i32 = 13;
348/// Length of the \[u8] holding a ML-DSA signing random value.
349pub const MLDSA_RND_LEN: usize = 32;
350/// Length of the \[u8] holding a ML-DSA tr value (which is the SHAKE256 hash of the public key).
351pub const MLDSA_TR_LEN: usize = 64;
352/// Length of the \[u8] holding a ML-DSA mu value.
353pub const MLDSA_MU_LEN: usize = 64;
354pub(crate) const POLY_T0PACKED_LEN: usize = 416;
355pub(crate) const POLY_T1PACKED_LEN: usize = 320;
356
357
358/* ML-DSA-44 params */
359
360/// Length of the \[u8] holding a ML-DSA-44 public key.
361pub const MLDSA44_PK_LEN: usize = 1312;
362/// Length of the \[u8] holding a ML-DSA-44 private key, which in this implementation is just a 32-byte seed.
363pub const MLDSA44_SK_LEN: usize = 32;
364/// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk]
365pub const MLDSA44_FULL_SK_LEN: usize = 2560;
366/// Length of the \[u8] holding a ML-DSA-44 signature value.
367pub const MLDSA44_SIG_LEN: usize = 2420;
368pub(crate) const MLDSA44_TAU: i32 = 39;
369pub(crate) const MLDSA44_LAMBDA: i32 = 128;
370pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17;
371pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88;
372pub(crate) const MLDSA44_k: usize = 4;
373pub(crate) const MLDSA44_l: usize = 4;
374pub(crate) const MLDSA44_ETA: usize = 2;
375pub(crate) const MLDSA44_BETA: i32 = 78;
376pub(crate) const MLDSA44_OMEGA: i32 = 80;
377
378// Useful derived values
379pub(crate) const MLDSA44_C_TILDE: usize = 32;
380pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576;
381pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192;
382pub(crate) const MLDSA44_S1_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_l; // 384 bytes
383pub(crate) const MLDSA44_S2_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_k; // 384 bytes
384pub(crate) const MLDSA44_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA44_k; // 768 bytes
385pub(crate) const MLDSA44_POLY_ETA_PACKED_LEN: usize = 32*3;
386pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128/4;
387
388// Alg 32
389// 1: π β 1 + bitlen (πΎ1 β 1)
390pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (πΎ1 β 1) )
391
392
393/* ML-DSA-65 params */
394
395/// Length of the \[u8] holding a ML-DSA-65 public key.
396pub const MLDSA65_PK_LEN: usize = 1952;
397/// Length of the \[u8] holding a ML-DSA-65 private key, which in this implementation is just a 32-byte seed.
398pub const MLDSA65_SK_LEN: usize = 32;
399/// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk]
400pub const MLDSA65_FULL_SK_LEN: usize = 4032;
401/// Length of the \[u8] holding a ML-DSA-65 signature value.
402pub const MLDSA65_SIG_LEN: usize = 3309;
403pub(crate) const MLDSA65_TAU: i32 = 49;
404pub(crate) const MLDSA65_LAMBDA: i32 = 192;
405pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19;
406pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32;
407pub(crate) const MLDSA65_k: usize = 6;
408pub(crate) const MLDSA65_l: usize = 5;
409pub(crate) const MLDSA65_ETA: usize = 4;
410pub(crate) const MLDSA65_BETA: i32 = 196;
411pub(crate) const MLDSA65_OMEGA: i32 = 55;
412
413// Useful derived values
414pub(crate) const MLDSA65_C_TILDE: usize = 48;
415pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640;
416pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128;
417pub(crate) const MLDSA65_S1_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_l; // 640 bytes
418pub(crate) const MLDSA65_S2_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_k; // 768 bytes
419pub(crate) const MLDSA65_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA65_k; // 1152 bytes
420pub(crate) const MLDSA65_POLY_ETA_PACKED_LEN: usize = 32*4;
421pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192/4;
422
423// Alg 32
424// 1: π β 1 + bitlen (πΎ1 β 1)
425pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640;
426
427
428
429/* ML-DSA-87 params */
430
431/// Length of the \[u8] holding a ML-DSA-87 public key.
432pub const MLDSA87_PK_LEN: usize = 2592;
433/// Length of the \[u8] holding a ML-DSA-87 private key, which in this implementation is just a 32-byte seed.
434pub const MLDSA87_SK_LEN: usize = 32;
435/// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk]
436pub const MLDSA87_FULL_SK_LEN: usize = 4896;
437/// Length of the \[u8] holding a ML-DSA-87 signature value.
438pub const MLDSA87_SIG_LEN: usize = 4627;
439pub(crate) const MLDSA87_TAU: i32 = 60;
440pub(crate) const MLDSA87_LAMBDA: i32 = 256;
441pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19;
442pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32;
443pub(crate) const MLDSA87_k: usize = 8;
444pub(crate) const MLDSA87_l: usize = 7;
445pub(crate) const MLDSA87_ETA: usize = 2;
446pub(crate) const MLDSA87_BETA: i32 = 120;
447pub(crate) const MLDSA87_OMEGA: i32 = 75;
448
449// Useful derived values
450pub(crate) const MLDSA87_C_TILDE: usize = 64;
451pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640;
452pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128;
453pub(crate) const MLDSA87_S1_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_l; // 672 bytes
454pub(crate) const MLDSA87_S2_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_k; // 768 bytes
455pub(crate) const MLDSA87_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA87_k; // 1024 bytes
456pub(crate) const MLDSA87_POLY_ETA_PACKED_LEN: usize = 32*3;
457pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256/4;
458
459// Alg 32
460// 1: π β 1 + bitlen (πΎ1 β 1)
461pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640;
462
463
464
465// Typedefs just to make the algorithms look more like the FIPS 204 sample code.
466pub(crate) type H = SHAKE256;
467pub(crate) type G = SHAKE128;
468
469
470/*** Pub Types ***/
471
472/// The ML-DSA-44 algorithm.
473pub type MLDSA44 = MLDSA<
474 MLDSA44_PK_LEN,
475 MLDSA44_SK_LEN,
476 MLDSA44_FULL_SK_LEN,
477 MLDSA44_SIG_LEN,
478 MLDSA44PublicKey,
479 MLDSA44PrivateKey,
480 MLDSA44_TAU,
481 MLDSA44_LAMBDA,
482 MLDSA44_GAMMA1,
483 MLDSA44_GAMMA2,
484 MLDSA44_k,
485 MLDSA44_l,
486 MLDSA44_ETA,
487 MLDSA44_BETA,
488 MLDSA44_OMEGA,
489 MLDSA44_C_TILDE,
490 MLDSA44_POLY_Z_PACKED_LEN,
491 MLDSA44_POLY_W1_PACKED_LEN,
492 MLDSA44_S1_PACKED_LEN,
493 MLDSA44_S2_PACKED_LEN,
494 MLDSA44_T1_PACKED_LEN,
495 MLDSA44_POLY_ETA_PACKED_LEN,
496 MLDSA44_LAMBDA_over_4,
497 MLDSA44_GAMMA1_MASK_LEN,
498>;
499
500impl Algorithm for MLDSA44 {
501 const ALG_NAME: &'static str = ML_DSA_44_NAME;
502 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
503}
504
505/// The ML-DSA-65 algorithm.
506pub type MLDSA65 = MLDSA<
507 MLDSA65_PK_LEN,
508 MLDSA65_SK_LEN,
509 MLDSA65_FULL_SK_LEN,
510 MLDSA65_SIG_LEN,
511 MLDSA65PublicKey,
512 MLDSA65PrivateKey,
513 MLDSA65_TAU,
514 MLDSA65_LAMBDA,
515 MLDSA65_GAMMA1,
516 MLDSA65_GAMMA2,
517 MLDSA65_k,
518 MLDSA65_l,
519 MLDSA65_ETA,
520 MLDSA65_BETA,
521 MLDSA65_OMEGA,
522 MLDSA65_C_TILDE,
523 MLDSA65_POLY_Z_PACKED_LEN,
524 MLDSA65_POLY_W1_PACKED_LEN,
525 MLDSA65_S1_PACKED_LEN,
526 MLDSA65_S2_PACKED_LEN,
527 MLDSA65_T1_PACKED_LEN,
528 MLDSA65_POLY_ETA_PACKED_LEN,
529 MLDSA65_LAMBDA_over_4,
530 MLDSA65_GAMMA1_MASK_LEN,
531>;
532
533impl Algorithm for MLDSA65 {
534 const ALG_NAME: &'static str = ML_DSA_65_NAME;
535 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
536}
537
538/// The ML-DSA-87 algorithm.
539pub type MLDSA87 = MLDSA<
540 MLDSA87_PK_LEN,
541 MLDSA87_SK_LEN,
542 MLDSA87_FULL_SK_LEN,
543 MLDSA87_SIG_LEN,
544 MLDSA87PublicKey,
545 MLDSA87PrivateKey,
546 MLDSA87_TAU,
547 MLDSA87_LAMBDA,
548 MLDSA87_GAMMA1,
549 MLDSA87_GAMMA2,
550 MLDSA87_k,
551 MLDSA87_l,
552 MLDSA87_ETA,
553 MLDSA87_BETA,
554 MLDSA87_OMEGA,
555 MLDSA87_C_TILDE,
556 MLDSA87_POLY_Z_PACKED_LEN,
557 MLDSA87_POLY_W1_PACKED_LEN,
558 MLDSA87_S1_PACKED_LEN,
559 MLDSA87_S2_PACKED_LEN,
560 MLDSA87_T1_PACKED_LEN,
561 MLDSA87_POLY_ETA_PACKED_LEN,
562 MLDSA87_LAMBDA_over_4,
563 MLDSA87_GAMMA1_MASK_LEN,
564>;
565
566impl Algorithm for MLDSA87 {
567 const ALG_NAME: &'static str = ML_DSA_87_NAME;
568 const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
569}
570
571/// The core internal implementation of the ML-DSA algorithm.
572/// This needs to be public for the compiler to be able to find it, but you shouldn't ever
573/// need to use this directly. Please use the named public types.
574pub struct MLDSA<
575 const PK_LEN: usize,
576 const SK_LEN: usize,
577 const FULL_SK_LEN: usize,
578 const SIG_LEN: usize,
579 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN> + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
580 SK: MLDSAPrivateKeyTrait<k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, PK_LEN, SK_LEN, FULL_SK_LEN>
581 + MLDSAPrivateKeyInternalTrait<LAMBDA, GAMMA2, k, l, ETA, S1_PACKED_LEN, S2_PACKED_LEN, PK_LEN, SK_LEN>,
582 const TAU: i32,
583 const LAMBDA: i32,
584 const GAMMA1: i32,
585 const GAMMA2: i32,
586 const k: usize,
587 const l: usize,
588 const ETA: usize,
589 const BETA: i32,
590 const OMEGA: i32,
591 const C_TILDE: usize,
592 const POLY_VEC_H_PACKED_LEN: usize,
593 const POLY_W1_PACKED_LEN: usize,
594 const S1_PACKED_LEN: usize,
595 const S2_PACKED_LEN: usize,
596 const T1_PACKED_LEN: usize,
597 const POLY_ETA_PACKED_LEN: usize,
598 const LAMBDA_over_4: usize,
599 const GAMMA1_MASK_LEN: usize,
600> {
601 _phantom: PhantomData<(PK, SK)>,
602
603 /// used for streaming the message for both signing and verifying
604 mu_builder: MuBuilder,
605
606 signer_rnd: Option<[u8; MLDSA_RND_LEN]>,
607
608 /// only used in streaming sign operations
609 sk: Option<SK>,
610
611 /// only used in streaming sign operations instead of sk
612 seed: Option<KeyMaterial<32>>,
613
614 /// only used in streaming verify operations
615 pk: Option<PK>,
616}
617
618impl<
619 const PK_LEN: usize,
620 const SK_LEN: usize,
621 const FULL_SK_LEN: usize,
622 const SIG_LEN: usize,
623 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN> + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
624 SK: MLDSAPrivateKeyTrait<k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, PK_LEN, SK_LEN, FULL_SK_LEN>
625 + MLDSAPrivateKeyInternalTrait<LAMBDA, GAMMA2, k, l, ETA, S1_PACKED_LEN, S2_PACKED_LEN, PK_LEN, SK_LEN>,
626 const TAU: i32,
627 const LAMBDA: i32,
628 const GAMMA1: i32,
629 const GAMMA2: i32,
630 const k: usize,
631 const l: usize,
632 const ETA: usize,
633 const BETA: i32,
634 const OMEGA: i32,
635 const C_TILDE: usize,
636 const POLY_Z_PACKED_LEN: usize,
637 const POLY_W1_PACKED_LEN: usize,
638 const S1_PACKED_LEN: usize,
639 const S2_PACKED_LEN: usize,
640 const T1_PACKED_LEN: usize,
641 const POLY_ETA_PACKED_LEN: usize,
642 const LAMBDA_over_4: usize,
643 const GAMMA1_MASK_LEN: usize,
644> MLDSA<
645 PK_LEN,
646 SK_LEN,
647 FULL_SK_LEN,
648 SIG_LEN,
649 PK,
650 SK,
651 TAU,
652 LAMBDA,
653 GAMMA1,
654 GAMMA2,
655 k,
656 l,
657 ETA,
658 BETA,
659 OMEGA,
660 C_TILDE,
661 POLY_Z_PACKED_LEN,
662 POLY_W1_PACKED_LEN,
663 S1_PACKED_LEN,
664 S2_PACKED_LEN,
665 T1_PACKED_LEN,
666 POLY_ETA_PACKED_LEN,
667 LAMBDA_over_4,
668 GAMMA1_MASK_LEN,
669>
670{
671 /// Should still be ok in FIPS mode
672 pub fn keygen_from_os_rng() -> Result<
673 (PK, SK),
674 SignatureError,
675 > {
676 let mut seed = KeyMaterial::<32>::new();
677 HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?;
678 Self::keygen_internal(&seed)
679 }
680 /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds.
681 ///
682 /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one
683 /// specifically takes a 32-byte [KeyMaterial256] and checks that it has [KeyType::Seed] and
684 /// the appropriate [SecurityStrength] for the requested ML-DSA parameter set.
685 /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it using
686 /// [KeyMaterial::from_key].
687 pub(crate) fn keygen_internal(
688 seed: &KeyMaterial<32>,
689 ) -> Result<
690 (PK, SK),
691 SignatureError,
692 > {
693 let sk = SK::from_keymaterial(seed)?;
694 let pk = sk.derive_pk();
695 let pk = PK::new(pk.rho, pk.t1_packed); // stupid conversion, but it gets around these overly-generified rust types
696 Ok((pk, sk))
697 }
698}
699
700impl<
701 const PK_LEN: usize,
702 const SK_LEN: usize,
703 const FULL_SK_LEN: usize,
704 const SIG_LEN: usize,
705 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN> + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
706 SK: MLDSAPrivateKeyTrait<k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, PK_LEN, SK_LEN, FULL_SK_LEN>
707 + MLDSAPrivateKeyInternalTrait<LAMBDA, GAMMA2, k, l, eta, S1_PACKED_LEN, S2_PACKED_LEN, PK_LEN, SK_LEN>,
708 const TAU: i32,
709 const LAMBDA: i32,
710 const GAMMA1: i32,
711 const GAMMA2: i32,
712 const k: usize,
713 const l: usize,
714 const eta: usize,
715 const BETA: i32,
716 const OMEGA: i32,
717 const C_TILDE: usize,
718 const POLY_Z_PACKED_LEN: usize,
719 const POLY_W1_PACKED_LEN: usize,
720 const S1_PACKED_LEN: usize,
721 const S2_PACKED_LEN: usize,
722 const T1_PACKED_LEN: usize,
723 const POLY_ETA_PACKED_LEN: usize,
724 const LAMBDA_over_4: usize,
725 const GAMMA1_MASK_LEN: usize,
726> MLDSATrait<PK_LEN, SK_LEN, FULL_SK_LEN, SIG_LEN, PK, SK, LAMBDA, GAMMA2, k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, eta> for MLDSA<
727 PK_LEN,
728 SK_LEN,
729 FULL_SK_LEN,
730 SIG_LEN,
731 PK,
732 SK,
733 TAU,
734 LAMBDA,
735 GAMMA1,
736 GAMMA2,
737 k,
738 l,
739 eta,
740 BETA,
741 OMEGA,
742 C_TILDE,
743 POLY_Z_PACKED_LEN,
744 POLY_W1_PACKED_LEN,
745 S1_PACKED_LEN,
746 S2_PACKED_LEN,
747 T1_PACKED_LEN,
748 POLY_ETA_PACKED_LEN,
749 LAMBDA_over_4,
750 GAMMA1_MASK_LEN,
751> {
752 /*** Key Generation and PK / SK consistency checks ***/
753
754 /// Imports a secret key from a seed.
755 fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> {
756 Self::keygen_internal(seed)
757 }
758 /// Imports a secret key from both a seed and an encoded_sk.
759 ///
760 /// This is a convenience function to expand the key from seed and compare it against
761 /// the provided `encoded_sk` using a constant-time equality check.
762 /// If everything checks out, the secret key is returned fully populated with pk and seed.
763 /// If the provided key and derived key don't match, an error is returned.
764 fn keygen_from_seed_and_encoded(
765 seed: &KeyMaterial<32>,
766 encoded_sk: &[u8; SK_LEN],
767 ) -> Result<
768 (PK, SK),
769 SignatureError,
770 > {
771 let (pk, sk) = Self::keygen_internal(seed)?;
772
773 let sk_from_bytes = SK::sk_decode(encoded_sk);
774
775 // MLDSAPrivateKey impls PartialEq with a constant-time equality check.
776 if sk != sk_from_bytes {
777 return Err(SignatureError::KeyGenError("Encoded key does not match generated key"));
778 }
779
780 Ok((pk, sk))
781 }
782 /// Given a public key and a secret key, check that the public key matches the secret key.
783 /// This is a sanity check that the public key was generated correctly from the secret key.
784 ///
785 /// At the current time, this is only possible if `sk` either contains a public key (in which case
786 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
787 /// (in which case a keygen_from_seed is run and then the pk's compared).
788 ///
789 /// Returns either `()` or [SignatureError::ConsistencyCheckFailed].
790 fn keypair_consistency_check(
791 pk: &PK,
792 sk: &SK,
793 ) -> Result<(), SignatureError> {
794 // This is maybe a computationally heavy way to compare them, but it works
795 let derived_pk = sk.derive_pk();
796 if derived_pk.compute_tr() == pk.compute_tr() {
797 Ok(())
798 } else {
799 Err(SignatureError::ConsistencyCheckFailed())
800 }
801 }
802 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
803 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
804 ///
805 /// This function, together with [MLDSATrait::sign_mu] perform a complete ML-DSA signature which is indistinguishable
806 /// from one produced by the one-shot sign APIs.
807 ///
808 /// The utility of this function is exactly as described
809 /// on Line 6 of Algorithm 7 of FIPS 204:
810 ///
811 /// message representative that may optionally be computed in a different cryptographic module
812 ///
813 /// The utility is when an extremely large message needs to be signed, where the message exists on one
814 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
815 /// causes operational concerns (this is common for example with network HSMs or sending large messages
816 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
817 /// contents of the message are sensitive and the signer does not want to transmit the message itself
818 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
819 ///
820 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
821 /// can be used with many hardware crypto modules.
822 ///
823 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
824 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
825 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
826 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
827 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
828 /// ML-DSA verifier.
829 ///
830 /// This function requires the public key hash `tr`, which can be computed from the public key
831 /// using [MLDSAPublicKeyTrait::compute_tr].
832 ///
833 /// For a streaming version of this, see [MuBuilder].
834 fn compute_mu_from_tr(
835 tr: &[u8; 64],
836 msg: &[u8],
837 ctx: Option<&[u8]>,
838 ) -> Result<[u8; 64], SignatureError> {
839 MuBuilder::compute_mu(tr, msg, ctx)
840 }
841 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the public key.
842 fn compute_mu_from_pk(
843 pk: &PK,
844 msg: &[u8],
845 ctx: Option<&[u8]>,
846 ) -> Result<[u8; 64], SignatureError> {
847 MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)
848 }
849 /// Same as [MLDSA::compute_mu_from_tr], but extracts tr from the private key.
850 fn compute_mu_from_sk(
851 sk: &SK,
852 msg: &[u8],
853 ctx: Option<&[u8]>,
854 ) -> Result<[u8; 64], SignatureError> {
855 MuBuilder::compute_mu(&sk.tr(), msg, ctx)
856 }
857 /// Performs an ML-DSA signature using the provided external message representative `mu`.
858 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
859 /// FIPS 204 itself, as well as subsequent FAQ documents.
860 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
861 fn sign_mu(
862 sk: &SK,
863 mu: &[u8; 64],
864 ) -> Result<[u8; SIG_LEN], SignatureError> {
865 let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN];
866 Self::sign_mu_out(sk, mu, &mut out)?;
867 Ok(out)
868 }
869 /// Performs an ML-DSA signature using the provided external message representative `mu`.
870 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
871 /// FIPS 204 itself, as well as subsequent FAQ documents.
872 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
873 ///
874 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
875 fn sign_mu_out(
876 sk: &SK,
877 mu: &[u8; 64],
878 output: &mut [u8; SIG_LEN],
879 ) -> Result<usize, SignatureError> {
880 let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
881 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
882
883 Self::sign_mu_deterministic_out(sk, mu, rnd, output)
884 }
885
886 fn sign_mu_deterministic(sk: &SK, mu: &[u8; 64], rnd: [u8; 32]) -> Result<[u8; SIG_LEN], SignatureError> {
887 let mut out = [0u8; SIG_LEN];
888 let bytes_written = Self::sign_mu_deterministic_out(sk, mu, rnd, &mut out)?;
889 debug_assert_eq!(bytes_written, SIG_LEN);
890 Ok(out)
891 }
892 /// This function is a mash-up of keyGen (Algorithm 6) and sign (Algorithm 7),
893 /// with a special emphasis on deriving values only as we need them, which in particular
894 /// means that we'll process matrices and vectors row or component-wise.
895 fn sign_mu_deterministic_out(
896 sk: &SK,
897 mu: &[u8; 64],
898 rnd: [u8; 32],
899 output: &mut [u8; SIG_LEN],
900 ) -> Result<usize, SignatureError> {
901
902 // I have tried to keep this as clean as possible for correspondence with the FIPS,
903 // but I have moved things around so that I can use unnamed scopes to limit how many
904 // stack variables are alive at the same time.
905
906 // 1: (π, πΎ, π‘π, π¬1, π¬2, π0) β skDecode(π π)
907 // to avoid having all of it in memory at the same time,
908 // we're gonna derive what we need as we need it.
909
910 // [Optimization Note]:
911 // s1 and s2 are normally part of the stored private key.
912 // We are going to need them many times through this function,
913 // so we'll compute them here and hold on to them in the compressed encoding specified in
914 // FIPS 204 Alg 17.
915 // We'll uncompress them as-needed, and only one polynomial at a time.
916 // You can avoid storing these in memory, but then all the sites where they are used
917 // will require calls to sk.compute_s1_row() and sk.compute_s2_row(), which are fairly expensive.
918 let s1_packed: [u8; S1_PACKED_LEN] = sk.compute_s1_packed();
919 let s2_packed: [u8; S2_PACKED_LEN] = sk.compute_s2_packed();
920
921 // 6: π β H(BytesToBits(π‘π)||π β², 64)
922 // skip: mu has already been provided
923
924 // Alg 7; 7: πβ³ β H(πΎ||πππ||π, 64)
925 let rho_p_p: [u8; 64] = {
926 let mut h = H::new();
927 h.absorb(sk.K());
928 h.absorb(&rnd);
929 h.absorb(mu);
930 let mut rho_p_p = [0u8; 64];
931 h.squeeze_out(&mut rho_p_p);
932 rho_p_p
933 };
934
935 // 8: π
β 0
936 // β· initialize counter π
937 let mut kappa: u16 = 0;
938
939 let z_offset = LAMBDA_over_4;
940 let hint_offset = LAMBDA_over_4 + l * POLY_Z_PACKED_LEN;
941
942 loop {
943 // FIPS 204 s. 6.2 allows:
944 // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value."
945 if kappa > 1000 * k as u16 {
946 return Err(SignatureError::GenericError(
947 "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.",
948 ));
949 }
950
951 // 11-15: derive c_tilde without materializing y_hat or w as full vectors.
952 let sig_val_c_tilde = { // scope for hash
953 let mut hash = H::new();
954 hash.absorb(mu);
955 for row in 0..k {
956 let mut w = compute_w_row::<l, GAMMA1, GAMMA1_MASK_LEN>(&sk.rho(), &rho_p_p, kappa, row);
957 w.high_bits::<GAMMA2>();
958 hash.absorb(&w.w1_encode::<POLY_W1_PACKED_LEN>());
959 }
960 let mut sig_val_c_tilde = [0u8; LAMBDA_over_4];
961 hash.squeeze_out(&mut sig_val_c_tilde);
962 sig_val_c_tilde
963 };
964 // 16: π β π
π β SampleInBall(c_tilde)
965 // 17: π_hat β NTT(π)
966 // optimization note: c_hat is used basically until the end, so we can't really scope it.
967 let mut c_hat = sample_in_ball::<LAMBDA_over_4, TAU>(&sig_val_c_tilde);
968 c_hat.ntt();
969
970 output.fill(0);
971 output[..LAMBDA_over_4].copy_from_slice(&sig_val_c_tilde);
972
973 let (z_chunks, z_remainder) = output[z_offset..z_offset + l * POLY_Z_PACKED_LEN]
974 .as_chunks_mut::<POLY_Z_PACKED_LEN>();
975 debug_assert_eq!(z_chunks.len(), l);
976 debug_assert_eq!(z_remainder.len(), 0);
977
978 // 18-23 (z path): compute and encode each z polynomial directly into the caller buffer.
979 let mut rejected = false;
980 for col in 0..l {
981 let z = match compute_z_component::<GAMMA1, GAMMA1_MASK_LEN, BETA>(
982 // [Optimization Note]:
983 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
984 // weirdly, in perf testing, this actually caused memory usage to go by a small amount;
985 // maybe because re-computing the intermediates adds more to the widest point of the alg?
986 // &sk.compute_s1_row(col),
987 &s_unpack::<eta>(&s1_packed, col),
988 &rho_p_p, &c_hat, kappa, col,
989 )? {
990 Some(z) => z,
991 None => {
992 rejected = true;
993 break;
994 }
995 };
996
997 bitpack_gamma1::<POLY_Z_PACKED_LEN, GAMMA1>(&z, &mut z_chunks[col]);
998 }
999
1000 if rejected {
1001 kappa += l as u16;
1002 continue;
1003 }
1004
1005 // 19-28 (hint path): recompute rows as needed and write the packed hint directly.
1006 let mut hint_count = 0usize;
1007 for row in 0..k {
1008 let mut w = compute_w_row::<l, GAMMA1, GAMMA1_MASK_LEN>(&sk.rho(), &rho_p_p, kappa, row);
1009 let mut tmp =
1010 match compute_w0cs2_component::<GAMMA2, BETA>(
1011 // [Optimization Note]:
1012 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
1013 // &sk.compute_s2_row(row),
1014 &s_unpack::<eta>(&s2_packed, row),
1015 &w, &c_hat) {
1016 Some(tmp) => tmp,
1017 None => {
1018 rejected = true;
1019 break;
1020 }
1021 };
1022
1023 let ct0 = match compute_ct0_component::<GAMMA2>(
1024 // [Optimization Note]:
1025 // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form.
1026 // &sk.compute_t0_row(row), &c_hat) {
1027 &sk.compute_t0_row(row, &s1_packed, &s2_packed), &c_hat) {
1028 Some(ct0) => ct0,
1029 None => {
1030 rejected = true;
1031 break;
1032 }
1033 };
1034
1035 tmp.add_ntt(&ct0);
1036 tmp.conditional_add_q();
1037
1038 w.high_bits::<GAMMA2>();
1039 let (hint_row, weight) = tmp.make_hint_row::<GAMMA2>(&w);
1040 let next_hint_count = hint_count + weight as usize;
1041 if next_hint_count > OMEGA as usize {
1042 rejected = true;
1043 break;
1044 }
1045
1046 for idx in 0..N {
1047 if hint_row[idx] != 0 {
1048 output[hint_offset + hint_count] = idx as u8;
1049 hint_count += 1;
1050 }
1051 }
1052 debug_assert_eq!(hint_count, next_hint_count);
1053 output[hint_offset + OMEGA as usize + row] = hint_count as u8;
1054 }
1055
1056 if rejected {
1057 kappa += l as u16;
1058 continue;
1059 }
1060
1061 break;
1062 }
1063
1064 Ok(SIG_LEN)
1065 }
1066
1067 fn sign_mu_deterministic_from_seed(seed: &KeyMaterial<32>, mu: &[u8; 64], rnd: [u8; 32]) -> Result<[u8; SIG_LEN], SignatureError> {
1068 let mut out = [0u8; SIG_LEN];
1069 SK::from_keymaterial(&seed)?;
1070 Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, &mut out)?;
1071 Ok(out)
1072 }
1073
1074 fn sign_mu_deterministic_from_seed_out(seed: &KeyMaterial<32>, mu: &[u8; 64], rnd: [u8; 32], output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError> {
1075 SK::from_keymaterial(&seed)?;
1076 Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, output)
1077 }
1078
1079
1080 /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow.
1081 /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final]
1082 fn set_signer_rnd(&mut self, rnd: [u8; 32]) {
1083 self.signer_rnd = Some(rnd);
1084 }
1085
1086 /// Alternative initialization of the streaming signer where you have your private key
1087 /// as a seed and you want to delay its expansion as late as possible for memory-usage reasons.
1088 fn sign_init_from_seed(seed: &KeyMaterial<32>, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1089 let (_pk, sk) = Self::keygen_from_seed(seed)?;
1090 Ok(
1091 Self {
1092 _phantom: PhantomData,
1093 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1094 signer_rnd: None,
1095 sk: None,
1096 seed: Some(seed.clone()),
1097 pk: None }
1098 )
1099 }
1100
1101 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1102 /// Internal function to verify a signature π for a formatted message πβ² .
1103 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1104 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1105 fn verify_mu_internal(
1106 pk: &PK,
1107 mu: &[u8; 64],
1108 sig: &[u8; SIG_LEN],
1109 ) -> bool {
1110 // 1: (π, π1) β pkDecode(ππ)
1111 // Already done -- the pk struct is already decoded
1112
1113 // 5: π β ExpandA(π)
1114 // β· π is generated and stored in NTT representation as π
1115 // We're gonna do this one row / polynomial at a time to reduce peak memory usage so that
1116 // the entirety of A_hat is never in memory at the same time.
1117
1118 // 6: π‘π β H(ππ, 64)
1119 // 7: π β (H(BytesToBits(π‘π)||π β², 64))
1120 // β· message representative that may optionally be
1121 // computed in a different cryptographic module
1122 // skip because this function is being handed mu
1123
1124 // 8: π β π
π β SampleInBall(c_tilde)
1125 let c = sample_in_ball::<LAMBDA_over_4, TAU>(unpack_c_tilde(sig));
1126
1127 // 12: π_tilde_p β H(π||w1Encode(π°1'), π/4)
1128 // β· hash it; this should match π_tilde
1129 let mut hash = H::new();
1130 hash.absorb(mu);
1131
1132 for row in 0..k {
1133 let mut wp_approx = match {
1134 // 9: π°β²_approx β NTTβ1(π_hat β NTT(π³) β NTT(π) β NTT(π1 β
2^π))
1135 compute_wp_approx_row::<GAMMA1, BETA, l, POLY_Z_PACKED_LEN, LAMBDA_over_4, SIG_LEN>(
1136 pk.rho(),
1137 sig,
1138 &pk.unpack_t1_row(row),
1139 &c,
1140 row)
1141 } {
1142 Ok(wp_approx) => wp_approx,
1143 // means the norm check on z failed
1144 Err(_) => return false,
1145 };
1146
1147 let h_i
1148 = match unpack_h_row::<GAMMA1, k, l, OMEGA, LAMBDA_over_4, POLY_Z_PACKED_LEN, SIG_LEN>
1149 (row, &sig) {
1150 Some(h_i) => h_i,
1151 // means there were more than OMEGA bits set in the hint
1152 None => return false,
1153 };
1154
1155 // 10: π°1β² β UseHint(π‘, π°'_approx)
1156 // β· reconstruction of signerβs commitment
1157 wp_approx.use_hint::<GAMMA2>(&h_i);
1158 hash.absorb(&wp_approx.w1_encode::<POLY_W1_PACKED_LEN>());
1159 }
1160
1161 let mut c_tilde_p = [0u8; LAMBDA_over_4];
1162 hash.squeeze_out(&mut c_tilde_p);
1163
1164 // verification probably doesn't technically need to be constant-time, but why not?
1165 // 13 (second half): return [[ ||π³||β < πΎ1 β π½]] and [[π Μ = πβ² ]]
1166 // note: the first half of this check (the norm check) is buried in unpack_z_row(),
1167 // which is called from compute_wp_approx_row()
1168 bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::<LAMBDA_over_4>(sig), &c_tilde_p)
1169 }
1170}
1171
1172/// Trait for all three of the ML-DSA algorithm variants.
1173pub trait MLDSATrait<
1174 const PK_LEN: usize,
1175 const SK_LEN: usize,
1176 const FULL_SK_LEN: usize,
1177 const SIG_LEN: usize,
1178 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN> + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
1179 SK: MLDSAPrivateKeyTrait<k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, PK_LEN, SK_LEN, FULL_SK_LEN>
1180 + MLDSAPrivateKeyInternalTrait<LAMBDA, GAMMA2, k, l, ETA, S1_PACKED_LEN, S2_PACKED_LEN, PK_LEN, SK_LEN>,
1181 const LAMBDA: i32,
1182 const GAMMA2: i32,
1183 const k: usize,
1184 const l: usize,
1185 const S1_PACKED_LEN: usize,
1186 const S2_PACKED_LEN: usize,
1187 const T1_PACKED_LEN: usize,
1188 const ETA: usize
1189> : Sized {
1190 /// Imports a secret key from a seed.
1191 fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>;
1192 /// Imports a secret key from both a seed and an encoded_sk.
1193 ///
1194 /// This is a convenience function to expand the key from seed and compare it against
1195 /// the provided `encoded_sk` using a constant-time equality check.
1196 /// If everything checks out, the secret key is returned fully populated with pk and seed.
1197 /// If the provided key and derived key don't match, an error is returned.
1198 fn keygen_from_seed_and_encoded(
1199 seed: &KeyMaterial<32>,
1200 encoded_sk: &[u8; SK_LEN],
1201 ) -> Result<
1202 (PK, SK),
1203 SignatureError,
1204 >;
1205 /// Given a public key and a secret key, check that the public key matches the secret key.
1206 /// This is a sanity check that the public key was generated correctly from the secret key.
1207 ///
1208 /// At the current time, this is only possible if `sk` either contains a public key (in which case
1209 /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
1210 /// (in which case a keygen_from_seed is run and then the pk's compared).
1211 ///
1212 /// Returns either `()` or [SignatureError::ConsistencyCheckFailed].
1213 fn keypair_consistency_check(
1214 pk: &PK,
1215 sk: &SK,
1216 ) -> Result<(), SignatureError>;
1217 /// This provides the first half of the "External Mu" interface to ML-DSA which is described
1218 /// in, and allowed under, NIST's FAQ that accompanies FIPS 204.
1219 ///
1220 /// This function, together with [MLDSATrait::sign_mu] perform a complete ML-DSA signature which is indistinguishable
1221 /// from one produced by the one-shot sign APIs.
1222 ///
1223 /// The utility of this function is exactly as described
1224 /// on Line 6 of Algorithm 7 of FIPS 204:
1225 ///
1226 /// message representative that may optionally be computed in a different cryptographic module
1227 ///
1228 /// The utility is when an extremely large message needs to be signed, where the message exists on one
1229 /// computing system and the private key to sign it is held on another and either the transfer time or bandwidth
1230 /// causes operational concerns (this is common for example with network HSMs or sending large messages
1231 /// to be signed by a smartcard communicating over near-field radio). Another use case is if the
1232 /// contents of the message are sensitive and the signer does not want to transmit the message itself
1233 /// for fear of leaking it via proxy logging and instead would prefer to only transmit a hash of it.
1234 ///
1235 /// Since "External Mu" mode is well-defined by FIPS 204 and allowed by NIST, the mu value produced here
1236 /// can be used with many hardware crypto modules.
1237 ///
1238 /// This "External Mu" mode of ML-DSA provides an alternative to the HashML-DSA algorithm in that it
1239 /// allows the message to be externally pre-hashed, however, unlike HashML-DSA, this is merely an optimization
1240 /// between the application holding the to-be-signed message and the cryptographic module holding the private key
1241 /// -- in particular, while HashML-DSA requires the verifier to know whether ML-DSA or HashML-DSA was used to sign
1242 /// the message, both "direct" ML-DSA and "External Mu" signatures can be verified with a standard
1243 /// ML-DSA verifier.
1244 ///
1245 /// This function requires the public key hash `tr`, which can be computed from the public key
1246 /// using [MLDSAPublicKeyTrait::compute_tr].
1247 ///
1248 /// For a streaming version of this, see [MuBuilder].
1249 fn compute_mu_from_tr(
1250 tr: &[u8; 64],
1251 msg: &[u8],
1252 ctx: Option<&[u8]>,
1253 ) -> Result<[u8; 64], SignatureError>;
1254 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the public key.
1255 fn compute_mu_from_pk(
1256 pk: &PK,
1257 msg: &[u8],
1258 ctx: Option<&[u8]>,
1259 ) -> Result<[u8; 64], SignatureError>;
1260 /// Same as [MLDSATrait::compute_mu_from_tr], but extracts tr from the private key.
1261 fn compute_mu_from_sk(
1262 sk: &SK,
1263 msg: &[u8],
1264 ctx: Option<&[u8]>,
1265 ) -> Result<[u8; 64], SignatureError>;
1266 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1267 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1268 /// FIPS 204 itself, as well as subsequent FAQ documents.
1269 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1270 fn sign_mu(
1271 sk: &SK,
1272 mu: &[u8; 64],
1273 ) -> Result<[u8; SIG_LEN], SignatureError>;
1274 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1275 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1276 /// FIPS 204 itself, as well as subsequent FAQ documents.
1277 /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG.
1278 ///
1279 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1280 fn sign_mu_out(
1281 sk: &SK,
1282 mu: &[u8; 64],
1283 output: &mut [u8; SIG_LEN],
1284 ) -> Result<usize, SignatureError>;
1285 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1286 /// (modified to take an externally-computed mu instead of M')
1287 ///
1288 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1289 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1290 /// FIPS 204 itself, as well as subsequent FAQ documents.
1291 ///
1292 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1293 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1294 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1295 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1296 /// of taking it by reference.
1297 ///
1298 /// Security note about deterministic mode:
1299 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1300 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1301 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
1302 /// If not, you may lose some privacy properties; for example, it becomes easy to tell if a signer
1303 /// has signed the same message twice or two different messages, or to tell if the same message
1304 /// has been signed by the same signer twice or two different signers.
1305 fn sign_mu_deterministic(
1306 sk: &SK,
1307 mu: &[u8; 64],
1308 rnd: [u8; 32],
1309 ) -> Result<[u8; SIG_LEN], SignatureError>;
1310 /// Algorithm 7 ML-DSA.Sign_internal(π π, πβ², πππ)
1311 /// (modified to take an externally-computed mu instead of M')
1312 ///
1313 /// Performs an ML-DSA signature using the provided external message representative `mu`.
1314 /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both
1315 /// FIPS 204 itself, as well as subsequent FAQ documents.
1316 /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG.
1317 ///
1318 /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing
1319 /// nonce from a source other than the library's default internal RNG, or who wish to use the
1320 /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`.
1321 /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead
1322 /// of taking it by reference.
1323 ///
1324 /// Security note about deterministic mode:
1325 /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204).
1326 /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that
1327 /// the responsibility is on you to ensure that your nonce `rnd` is unique per signature.
1328 /// If not, you may lose some privacy properties; for example, it becomes easy to tell if a signer
1329 /// has signed the same message twice or two different messages, or to tell if the same message
1330 /// has been signed by the same signer twice or two different signers.
1331 ///
1332 /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
1333 fn sign_mu_deterministic_out(
1334 sk: &SK,
1335 mu: &[u8; 64],
1336 rnd: [u8; 32],
1337 output: &mut [u8; SIG_LEN],
1338 ) -> Result<usize, SignatureError>;
1339 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1340 /// memory usage by never having the full secret key in memory at the same time,
1341 /// and by deriving intermediate values piece-wise as needed.
1342 fn sign_mu_deterministic_from_seed(
1343 seed: &KeyMaterial<32>,
1344 mu: &[u8; 64],
1345 rnd: [u8; 32],
1346 ) -> Result<[u8; SIG_LEN], SignatureError>;
1347 /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak
1348 /// memory usage by never having the full secret key in memory at the same time,
1349 /// and by deriving intermediate values piece-wise as needed.
1350 fn sign_mu_deterministic_from_seed_out(
1351 seed: &KeyMaterial<32>,
1352 mu: &[u8; 64],
1353 rnd: [u8; 32],
1354 output: &mut [u8; SIG_LEN],
1355 ) -> Result<usize, SignatureError>;
1356 /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow.
1357 /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final]
1358 fn set_signer_rnd(&mut self, rnd: [u8; 32]);
1359 /// An alternate way to start the streaming signing mode by providing a private key seed instead of an expanded private key
1360 fn sign_init_from_seed(seed: &KeyMaterial<32>, ctx: Option<&[u8]>) -> Result<Self, SignatureError>;
1361 /// Algorithm 8 ML-DSA.Verify_internal(ππ, πβ², π)
1362 /// Internal function to verify a signature π for a formatted message πβ² .
1363 /// Input: Public key ππ β πΉ32+32π(bitlen (πβ1)βπ) and message πβ² β {0, 1}β .
1364 /// Input: Signature π β πΉπ/4+ββ
32β
(1+bitlen (πΎ1β1))+π+π.
1365 fn verify_mu_internal(
1366 pk: &PK,
1367 mu: &[u8; 64],
1368 sig: &[u8; SIG_LEN],
1369 ) -> bool;
1370}
1371
1372impl<
1373 const PK_LEN: usize,
1374 const SK_LEN: usize,
1375 const FULL_SK_LEN: usize,
1376 const SIG_LEN: usize,
1377 PK: MLDSAPublicKeyTrait<k, T1_PACKED_LEN, PK_LEN> + MLDSAPublicKeyInternalTrait<k, T1_PACKED_LEN, PK_LEN>,
1378 SK: MLDSAPrivateKeyTrait<k, l, S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, PK_LEN, SK_LEN, FULL_SK_LEN>
1379 + MLDSAPrivateKeyInternalTrait<LAMBDA, GAMMA2, k, l, ETA, S1_PACKED_LEN, S2_PACKED_LEN, PK_LEN, SK_LEN>,
1380 const TAU: i32,
1381 const LAMBDA: i32,
1382 const GAMMA1: i32,
1383 const GAMMA2: i32,
1384 const k: usize,
1385 const l: usize,
1386 const ETA: usize,
1387 const BETA: i32,
1388 const OMEGA: i32,
1389 const C_TILDE: usize,
1390 const POLY_Z_PACKED_LEN: usize,
1391 const POLY_W1_PACKED_LEN: usize,
1392 const S1_PACKED_LEN: usize,
1393 const S2_PACKED_LEN: usize,
1394 const T1_PACKED_LEN: usize,
1395 const POLY_ETA_PACKED_LEN: usize,
1396 const LAMBDA_over_4: usize,
1397 const GAMMA1_MASK_LEN: usize,
1398> Signature<PK, SK, PK_LEN, SK_LEN, SIG_LEN> for MLDSA<
1399 PK_LEN,
1400 SK_LEN,
1401 FULL_SK_LEN,
1402 SIG_LEN,
1403 PK,
1404 SK,
1405 TAU,
1406 LAMBDA,
1407 GAMMA1,
1408 GAMMA2,
1409 k,
1410 l,
1411 ETA,
1412 BETA,
1413 OMEGA,
1414 C_TILDE,
1415 POLY_Z_PACKED_LEN,
1416 POLY_W1_PACKED_LEN,
1417 S1_PACKED_LEN,
1418 S2_PACKED_LEN,
1419 T1_PACKED_LEN,
1420 POLY_ETA_PACKED_LEN,
1421 LAMBDA_over_4,
1422 GAMMA1_MASK_LEN,
1423> {
1424
1425 fn keygen() -> Result<(PK, SK), SignatureError> {
1426 Self::keygen_from_os_rng()
1427 }
1428
1429 fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> {
1430 let mut out = [0u8; SIG_LEN];
1431 Self::sign_out(sk, msg, ctx, &mut out)?;
1432
1433 Ok(out)
1434 }
1435
1436 fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError> {
1437 let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?;
1438 let bytes_written = Self::sign_mu_out(sk, &mu, output)?;
1439
1440 Ok(bytes_written)
1441 }
1442
1443 fn sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1444 Ok(
1445 Self {
1446 _phantom: PhantomData,
1447 mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?,
1448 signer_rnd: None,
1449 sk: Some(sk.clone()),
1450 seed: None,
1451 pk: None }
1452 )
1453 }
1454
1455 fn sign_update(&mut self, msg_chunk: &[u8]) {
1456 self.mu_builder.do_update(msg_chunk);
1457 }
1458
1459 fn sign_final(self) -> Result<[u8; SIG_LEN], SignatureError> {
1460 let mut out = [0u8; SIG_LEN];
1461 self.sign_final_out(&mut out)?;
1462 Ok(out)
1463 }
1464
1465 fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError> {
1466 let mu = self.mu_builder.do_final();
1467
1468 if self.sk.is_none() && self.seed.is_none() {
1469 return Err(SignatureError::GenericError("Somehow you managed to construct a streaming signer without a private key, impressive!"))
1470 }
1471
1472 if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) }
1473 let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap();
1474
1475 if self.sk.is_some() {
1476 if self.signer_rnd.is_none() {
1477 Self::sign_mu_out(&self.sk.unwrap(), &mu, output_sized)
1478 } else {
1479 Self::sign_mu_deterministic_out(&self.sk.unwrap(), &mu, self.signer_rnd.unwrap(), output_sized)
1480 }
1481 } else if self.seed.is_some() {
1482 let rnd = if self.signer_rnd.is_some() {
1483 self.signer_rnd.unwrap()
1484 } else {
1485 let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
1486 HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
1487 rnd
1488 };
1489 Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output_sized)
1490 } else { unreachable!() }
1491 }
1492
1493 fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> {
1494 let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?;
1495
1496 if sig.len() < SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1497 if Self::verify_mu_internal(pk, &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1498 Ok(())
1499 } else {
1500 Err(SignatureError::SignatureVerificationFailed)
1501 }
1502 }
1503
1504 fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1505 Ok(
1506 Self {
1507 _phantom: Default::default(),
1508 mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?,
1509 signer_rnd: None,
1510 sk: None,
1511 seed: None,
1512 pk: Some(pk.clone()) }
1513 )
1514 }
1515
1516 fn verify_update(&mut self, msg_chunk: &[u8]) {
1517 self.mu_builder.do_update(msg_chunk);
1518 }
1519
1520 fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> {
1521 let mu = self.mu_builder.do_final();
1522
1523 assert!(self.pk.is_some(), "Somehow you managed to construct a streaming verifier without a public key, impressive!");
1524
1525 if sig.len() < SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) }
1526 if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig[..SIG_LEN].try_into().unwrap()) {
1527 Ok(())
1528 } else {
1529 Err(SignatureError::SignatureVerificationFailed)
1530 }
1531 }
1532}
1533
1534
1535/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204.
1536/// Provides a stateful version of [MLDSATrait::compute_mu_from_pk] and [MLDSATrait::compute_mu_from_tr]
1537/// that supports streaming
1538/// large to-be-signed messages.
1539///
1540/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA
1541/// does not benefit from allowing external construction of the message representative mu.
1542/// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function
1543/// and providing that to HashMLDSA via [PHSignature::sign_ph].
1544pub struct MuBuilder {
1545 h: H,
1546}
1547
1548impl MuBuilder {
1549 /// Algorithm 7
1550 /// 6: π β H(BytesToBits(π‘π)||πβ², 64)
1551 pub fn compute_mu(tr: &[u8; 64],msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; 64], SignatureError> {
1552 let mut mu_builder = MuBuilder::do_init(&tr, ctx)?;
1553 mu_builder.do_update(msg);
1554 let mu = mu_builder.do_final();
1555
1556 Ok(mu)
1557 }
1558
1559 /// This function requires the public key hash `tr`, which can be computed from the public key
1560 /// using [MLDSAPublicKeyTrait::compute_tr].
1561 pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result<Self, SignatureError> {
1562 let ctx = match ctx { Some(ctx) => ctx, None => &[] };
1563
1564 // Algorithm 2
1565 // 1: if |ππ‘π₯| > 255 then
1566 if ctx.len() > 255 {
1567 return Err(SignatureError::LengthError("ctx value is longer than 255 bytes"));
1568 }
1569
1570 // Algorithm 7
1571 // 6: π β H(BytesToBits(π‘π)||π', 64)
1572 let mut mb = Self { h: H::new() };
1573 mb.h.absorb(tr);
1574
1575 // Algorithm 2
1576 // 10: πβ² β BytesToBits(IntegerToBytes(0, 1) β₯ IntegerToBytes(|ππ‘π₯|, 1) β₯ ππ‘π₯) β₯ π
1577 // all done together
1578 mb.h.absorb(&[0u8]);
1579 mb.h.absorb(&[ctx.len() as u8]);
1580 mb.h.absorb(ctx);
1581
1582 // now ready to absorb M
1583 Ok(mb)
1584 }
1585
1586 /// Stream a chunk of the message.
1587 pub fn do_update(&mut self, msg_chunk: &[u8]) {
1588 self.h.absorb(msg_chunk);
1589 }
1590
1591 /// Finalize and return the mu value.
1592 pub fn do_final(mut self) -> [u8; 64] {
1593 // Completion of
1594 // Algorithm 7
1595 // 6: π β H(BytesToBits(π‘π)||π β², 64)
1596 let mut mu = [0u8; 64];
1597 self.h.squeeze_out(&mut mu);
1598
1599 mu
1600 }
1601}