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