Skip to main content

ml_dsa/
signing.rs

1//! ML-DSA `SigningKey` and `ExpandedSigningKey`.
2//!
3//! These types implement signature generation.
4
5use crate::{
6    B32, B64, ExpandedSigningKeyBytes, MlDsaParams, MuBuilder, Seed, Signature, VerifyingKey,
7    algebra::{AlgebraExt, NttMatrix, NttVector, Vector},
8    crypto::H,
9    hint::Hint,
10    ntt::{Ntt, NttInverse},
11    param::{SamplingSize, SpecQ},
12    sampling::{expand_a, expand_mask, expand_s, sample_in_ball},
13};
14use common::{KeyExport, KeyInit, KeySizeUser, typenum::U32};
15use core::fmt;
16use ctutils::{Choice, CtEq};
17use hybrid_array::typenum::Unsigned;
18use module_lattice::MaybeBox;
19use shake::Shake256;
20use signature::{DigestSigner, Error, MultipartSigner, Signer};
21
22#[cfg(feature = "rand_core")]
23use {
24    common::Generate,
25    signature::{
26        RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner,
27        rand_core::TryCryptoRng,
28    },
29};
30
31#[cfg(feature = "zeroize")]
32use zeroize::{Zeroize, ZeroizeOnDrop};
33
34mod steps;
35
36/// ML-DSA signing key (i.e. private/secret key).
37///
38/// This type is initialized through a [`Seed`], and can be used to generate ML-DSA signatures.
39#[derive(Clone)]
40pub struct SigningKey<P: MlDsaParams> {
41    /// The expanded form of the signing key.
42    expanded_key: MaybeBox<ExpandedSigningKey<P>>,
43
44    /// The seed this signing key was derived from
45    seed: MaybeBox<Seed>,
46
47    /// When the `alloc` feature is available, precompute the [`VerifyingKey`].
48    #[cfg(feature = "alloc")]
49    verifying_key: VerifyingKey<P>,
50}
51
52impl<P: MlDsaParams> SigningKey<P> {
53    /// Deterministically generate a signing key pair from the specified [`Seed`].
54    ///
55    /// This method reflects the `ML-DSA.KeyGen_internal` algorithm from FIPS 204 (Algorithm 6).
56    #[must_use]
57    pub fn from_seed(xi: &Seed) -> Self {
58        // Derive seeds
59        let mut h = H::default()
60            .absorb(xi)
61            .absorb(&[P::K::U8])
62            .absorb(&[P::L::U8]);
63
64        let rho: B32 = h.squeeze_new();
65        let rhop: B64 = h.squeeze_new();
66        let K: B32 = h.squeeze_new();
67
68        // Sample private key components
69        let A_hat = expand_a::<P::K, P::L>(&rho);
70        let s1 = expand_s::<P::L>(&rhop, P::Eta::ETA, 0);
71        let s2 = expand_s::<P::K>(&rhop, P::Eta::ETA, P::L::USIZE);
72
73        // Compute derived values
74        let As1_hat = &A_hat * &s1.ntt();
75        let t = &As1_hat.ntt_inverse() + &s2;
76
77        // Compress and encode
78        let (t1, t0) = t.power2round();
79
80        let enc = VerifyingKey::<P>::encode_internal(&rho, &t1);
81        let tr: B64 = H::default().absorb(&enc).squeeze_new();
82
83        #[cfg(feature = "alloc")]
84        let verifying_key = VerifyingKey::new(rho.clone(), t1, A_hat.clone(), Some(enc));
85
86        let expanded_key = ExpandedSigningKey::new(rho, K, tr, s1, s2, t0, A_hat);
87
88        SigningKey {
89            expanded_key: MaybeBox::new(expanded_key),
90            seed: MaybeBox::new(xi.clone()),
91            #[cfg(feature = "alloc")]
92            verifying_key,
93        }
94    }
95
96    /// Borrow the [`Seed`] value: 32-bytes which can be used to reconstruct the [`SigningKey`].
97    ///
98    /// <div class="warning">
99    /// <b>Warning</b>
100    ///
101    /// This value is key material. Please treat it with care.
102    /// </div>
103    #[inline]
104    #[must_use]
105    pub fn as_seed(&self) -> &Seed {
106        &self.seed
107    }
108
109    /// Serialize the [`Seed`] value: 32-bytes which can be used to reconstruct the [`SigningKey`].
110    ///
111    /// <div class="warning">
112    /// <b>Warning</b>
113    ///
114    /// This value is key material. Please treat it with care.
115    /// </div>
116    #[inline]
117    #[must_use]
118    pub fn to_seed(&self) -> Seed {
119        *self.seed
120    }
121
122    /// The expanded form of the signing key.
123    #[doc(hidden)]
124    #[must_use]
125    pub fn expanded_key(&self) -> &ExpandedSigningKey<P> {
126        &self.expanded_key
127    }
128}
129
130impl<P: MlDsaParams> KeySizeUser for SigningKey<P> {
131    type KeySize = U32;
132}
133
134impl<P: MlDsaParams> KeyInit for SigningKey<P> {
135    fn new(seed: &Seed) -> Self {
136        Self::from_seed(seed)
137    }
138}
139
140impl<P: MlDsaParams> KeyExport for SigningKey<P> {
141    fn to_bytes(&self) -> Seed {
142        self.to_seed()
143    }
144}
145
146/// Algorithm 1: `ML-DSA.KeyGen()`.
147#[cfg(feature = "rand_core")]
148impl<P: MlDsaParams> Generate for SigningKey<P> {
149    fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
150        let seed = Seed::try_generate_from_rng(rng)?;
151        Ok(Self::from_seed(&seed))
152    }
153}
154
155impl<P: MlDsaParams> fmt::Debug for SigningKey<P> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_struct("SigningKey").finish_non_exhaustive()
158    }
159}
160
161// NOTE: when the `alloc` feature is enabled, we receive a blanket impl of `Keypair` via the impl
162// of the `KeypairRef` trait which simply clones the precomputed verifying key, providing equivalent
163// functionality and thus this is actually still an additive use of features.
164#[cfg(not(feature = "alloc"))]
165impl<P: MlDsaParams> signature::Keypair for SigningKey<P> {
166    type VerifyingKey = VerifyingKey<P>;
167    fn verifying_key(&self) -> VerifyingKey<P> {
168        self.expanded_key.verifying_key()
169    }
170}
171
172#[cfg(feature = "alloc")]
173impl<P: MlDsaParams> AsRef<VerifyingKey<P>> for SigningKey<P> {
174    fn as_ref(&self) -> &VerifyingKey<P> {
175        &self.verifying_key
176    }
177}
178
179#[cfg(feature = "alloc")]
180impl<P: MlDsaParams> signature::KeypairRef for SigningKey<P> {
181    type VerifyingKey = VerifyingKey<P>;
182}
183
184/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and
185/// only supports signing with an empty context string.
186impl<P: MlDsaParams> Signer<Signature<P>> for SigningKey<P> {
187    fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, Error> {
188        self.try_multipart_sign(&[msg])
189    }
190}
191
192/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and
193/// only supports signing with an empty context string.
194impl<P: MlDsaParams> MultipartSigner<Signature<P>> for SigningKey<P> {
195    fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, Error> {
196        self.expanded_key.raw_sign_deterministic(msg, &[])
197    }
198}
199
200/// The `DigestSigner` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA
201/// with a pre-computed μ, and only supports signing with an empty context string.
202impl<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for SigningKey<P> {
203    fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
204        &self,
205        f: F,
206    ) -> Result<Signature<P>, Error> {
207        self.expanded_key.try_sign_digest(&f)
208    }
209}
210
211impl<P: MlDsaParams> PartialEq for SigningKey<P> {
212    fn eq(&self, other: &Self) -> bool {
213        self.ct_eq(other).into()
214    }
215}
216
217impl<P: MlDsaParams> CtEq for SigningKey<P> {
218    fn ct_eq(&self, other: &Self) -> Choice {
219        self.expanded_key
220            .ct_eq(&other.expanded_key)
221            .and(self.seed.ct_eq(&other.seed))
222    }
223}
224
225impl<P: MlDsaParams> Drop for SigningKey<P> {
226    fn drop(&mut self) {
227        // NOTE: `expanded_key` has its own zeroizing `Drop` impl so we just need to clear `seed`
228        #[cfg(feature = "zeroize")]
229        self.seed.zeroize();
230    }
231}
232
233#[cfg(feature = "zeroize")]
234impl<P: MlDsaParams> ZeroizeOnDrop for SigningKey<P> {}
235
236/// An ML-DSA signing key
237#[derive(Clone)]
238pub struct ExpandedSigningKey<P: MlDsaParams> {
239    rho: B32,
240    K: B32,
241    pub(crate) tr: B64,
242    s1: Vector<P::L>,
243    s2: Vector<P::K>,
244    t0: Vector<P::K>,
245
246    // Derived values
247    s1_hat: NttVector<P::L>,
248    s2_hat: NttVector<P::K>,
249    t0_hat: NttVector<P::K>,
250    A_hat: NttMatrix<P::K, P::L>,
251}
252
253impl<P: MlDsaParams> ExpandedSigningKey<P> {
254    pub(crate) fn new(
255        rho: B32,
256        K: B32,
257        tr: B64,
258        s1: Vector<P::L>,
259        s2: Vector<P::K>,
260        t0: Vector<P::K>,
261        A_hat: NttMatrix<P::K, P::L>,
262    ) -> Self {
263        let s1_hat = s1.ntt();
264        let s2_hat = s2.ntt();
265        let t0_hat = t0.ntt();
266
267        Self {
268            rho,
269            K,
270            tr,
271            s1,
272            s2,
273            t0,
274
275            s1_hat,
276            s2_hat,
277            t0_hat,
278            A_hat,
279        }
280    }
281
282    #[inline]
283    fn new_expand_a(
284        rho: B32,
285        K: B32,
286        tr: B64,
287        s1: Vector<P::L>,
288        s2: Vector<P::K>,
289        t0: Vector<P::K>,
290    ) -> Self {
291        let A_hat = expand_a(&rho);
292        Self::new(rho, K, tr, s1, s2, t0, A_hat)
293    }
294
295    /// Deterministically generate an expanded signing key from the specified seed.
296    ///
297    /// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a
298    /// signing key.
299    #[must_use]
300    #[inline]
301    pub fn from_seed(seed: &Seed) -> Self {
302        let kp = SigningKey::from_seed(seed);
303        (*kp.expanded_key).clone()
304    }
305
306    /// This method reflects the ML-DSA.Sign_internal algorithm from FIPS 204. It does not
307    /// include the domain separator that distinguishes between the normal and pre-hashed cases,
308    /// and it does not separate the context string from the rest of the message.
309    ///
310    /// # Panics
311    ///
312    /// Panics if the rejection sampling loop fails to find a valid signature.
313    // Algorithm 7 ML-DSA.Sign_internal
314    // TODO(RLB) Only expose based on a feature. Tests need access, but normal code shouldn't.
315    pub fn sign_internal(&self, Mp: &[&[u8]], rnd: &B32) -> Signature<P>
316    where
317        P: MlDsaParams,
318    {
319        let mu = MuBuilder::internal(&self.tr, Mp);
320        self.raw_sign_mu(&mu, rnd)
321    }
322
323    pub(crate) fn raw_sign_mu(&self, mu: &B64, rnd: &B32) -> Signature<P>
324    where
325        P: MlDsaParams,
326    {
327        self.try_raw_sign_mu(mu, rnd)
328            .expect("Rejection sampling failed to find a valid signature")
329    }
330
331    fn try_raw_sign_mu(&self, mu: &B64, rnd: &B32) -> Result<Signature<P>, Error> {
332        // Compute the private random seed
333        let rhopp: B64 = H::default()
334            .absorb(&self.K)
335            .absorb(rnd)
336            .absorb(mu)
337            .squeeze_new();
338
339        // Rejection sampling loop
340        for kappa in signing_nonces::<P>() {
341            let y = expand_mask::<P::L, P::Gamma1>(&rhopp, kappa);
342            let w = (&self.A_hat * &y.ntt()).ntt_inverse();
343            let w1 = w.high_bits::<P::TwoGamma2>();
344
345            let w1_tilde = P::encode_w1(&w1);
346            let c_tilde = H::default()
347                .absorb(mu)
348                .absorb(&w1_tilde)
349                .squeeze_new::<P::Lambda>();
350            let c = sample_in_ball(&c_tilde, P::TAU);
351            let c_hat = c.ntt();
352
353            let steps::Response { z, cs2, r0 } =
354                steps::response::<P>(&y, &w, &c_hat, &self.s1_hat, &self.s2_hat);
355
356            if z.infinity_norm() >= P::GAMMA1_MINUS_BETA
357                || r0.infinity_norm() >= P::GAMMA2_MINUS_BETA
358            {
359                continue;
360            }
361
362            let (ct0, w_cs2_ct0) = steps::hint_polynomials::<P>(&w, &cs2, &c_hat, &self.t0_hat);
363            let minus_ct0 = -&ct0;
364            let h = Hint::<P>::new(&minus_ct0, &w_cs2_ct0);
365
366            if ct0.infinity_norm() >= P::Gamma2::U32 || h.hamming_weight() > P::Omega::USIZE {
367                continue;
368            }
369
370            let z = MaybeBox::new(z.mod_plus_minus::<SpecQ>());
371            return Ok(Signature { c_tilde, z, h });
372        }
373
374        Err(Error::new())
375    }
376
377    /// This method reflects the randomized ML-DSA.Sign algorithm.
378    ///
379    /// # Errors
380    ///
381    /// This method will return an opaque error if the context string is more than 255 bytes long,
382    /// if it fails to get enough randomness, or if the rejection sampling loop fails to find a
383    /// valid signature.
384    // Algorithm 2 ML-DSA.Sign
385    #[cfg(feature = "rand_core")]
386    pub fn sign_randomized<R: TryCryptoRng + ?Sized>(
387        &self,
388        M: &[u8],
389        ctx: &[u8],
390        rng: &mut R,
391    ) -> Result<Signature<P>, Error> {
392        self.raw_sign_randomized(&[M], ctx, rng)
393    }
394
395    #[cfg(feature = "rand_core")]
396    fn raw_sign_randomized<R: TryCryptoRng + ?Sized>(
397        &self,
398        Mp: &[&[u8]],
399        ctx: &[u8],
400        rng: &mut R,
401    ) -> Result<Signature<P>, Error> {
402        if ctx.len() > 255 {
403            return Err(Error::new());
404        }
405
406        let mut rnd = B32::default();
407        rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?;
408
409        let mu = MuBuilder::new(&self.tr, ctx).message(Mp);
410        self.try_raw_sign_mu(&mu, &rnd)
411    }
412
413    /// This method reflects the randomized ML-DSA.Sign algorithm with a pre-computed μ.
414    ///
415    /// # Errors
416    ///
417    /// This method can return an opaque error if it fails to get enough randomness, or if the
418    /// rejection sampling loop fails to find a valid signature.
419    // Algorithm 2 ML-DSA.Sign (optional pre-computed μ variant)
420    #[cfg(feature = "rand_core")]
421    pub fn sign_mu_randomized<R: TryCryptoRng + ?Sized>(
422        &self,
423        mu: &B64,
424        rng: &mut R,
425    ) -> Result<Signature<P>, Error> {
426        let mut rnd = B32::default();
427        rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?;
428
429        self.try_raw_sign_mu(mu, &rnd)
430    }
431
432    /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm.
433    ///
434    /// # Errors
435    ///
436    /// This method will return an opaque error if the context string is more than 255 bytes long.
437    /// It will also return an opaque error if the rejection sampling loop fails to find a valid
438    /// signature.
439    // Algorithm 2 ML-DSA.Sign (optional deterministic variant)
440    pub fn sign_deterministic(&self, M: &[u8], ctx: &[u8]) -> Result<Signature<P>, Error> {
441        self.raw_sign_deterministic(&[M], ctx)
442    }
443
444    /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm with a
445    /// pre-computed μ.
446    ///
447    /// # Panics
448    ///
449    /// Panics if the rejection sampling loop fails to find a valid signature.
450    // Algorithm 2 ML-DSA.Sign (optional deterministic and pre-computed μ variant)
451    pub fn sign_mu_deterministic(&self, mu: &B64) -> Signature<P> {
452        let rnd = B32::default();
453        self.raw_sign_mu(mu, &rnd)
454    }
455
456    fn raw_sign_deterministic(&self, Mp: &[&[u8]], ctx: &[u8]) -> Result<Signature<P>, Error> {
457        if ctx.len() > 255 {
458            return Err(Error::new());
459        }
460
461        let mu = MuBuilder::new(&self.tr, ctx).message(Mp);
462        self.try_raw_sign_mu(&mu, &B32::default())
463    }
464
465    /// This auxiliary function derives a `VerifyingKey` from a bare
466    /// `ExpandedSigningKey` (even in the absence of the original seed).
467    ///
468    /// This is a utility function that is useful when importing the private key
469    /// from an external source which does not export the seed and does not
470    /// provide the precomputed public key associated with the private key
471    /// itself.
472    ///
473    /// `ExpandedSigningKey` implements `signature::Keypair`: this inherent method is
474    /// retained for convenience, so it is available for callers even when the
475    /// `signature::Keypair` trait is out-of-scope.
476    pub fn verifying_key(&self) -> VerifyingKey<P> {
477        let kp: &dyn signature::Keypair<VerifyingKey = VerifyingKey<P>> = self;
478        kp.verifying_key()
479    }
480
481    /// DEPRECATED: decode the key from an appropriately sized byte array.
482    ///
483    /// Note that this form is deprecated in practice; prefer to use [`ExpandedSigningKey::from_seed`].
484    ///
485    /// <div class="warning">
486    /// <b>Panics</b>
487    ///
488    /// This API does not validate expanded signing keys and can potentially panic if keys are
489    /// malformed or maliciously generated.
490    ///
491    /// To avoid panics, use [`ExpandedSigningKey::from_seed`] instead.
492    /// </div>
493    // Algorithm 25 skDecode
494    #[deprecated(since = "0.1.0", note = "use `ExpandedSigningKey::from_seed` instead")]
495    pub fn from_expanded(enc: &ExpandedSigningKeyBytes<P>) -> Self
496    where
497        P: MlDsaParams,
498    {
499        let (rho, K, tr, s1_enc, s2_enc, t0_enc) = P::split_sk(enc);
500        Self::new_expand_a(
501            rho.clone(),
502            K.clone(),
503            tr.clone(),
504            P::decode_s1(s1_enc),
505            P::decode_s2(s2_enc),
506            P::decode_t0(t0_enc),
507        )
508    }
509
510    /// DEPRECATED: encode the key in a fixed-size byte array.
511    ///
512    /// Note that this form is deprecated in practice; prefer to use [`SigningKey::to_seed`].
513    // Algorithm 24 skEncode
514    #[deprecated(since = "0.1.0", note = "use `SigningKey::to_seed` instead")]
515    pub fn to_expanded(&self) -> ExpandedSigningKeyBytes<P>
516    where
517        P: MlDsaParams,
518    {
519        let s1_enc = P::encode_s1(&self.s1);
520        let s2_enc = P::encode_s2(&self.s2);
521        let t0_enc = P::encode_t0(&self.t0);
522        P::concat_sk(
523            self.rho.clone(),
524            self.K.clone(),
525            self.tr.clone(),
526            s1_enc,
527            s2_enc,
528            t0_enc,
529        )
530    }
531}
532
533/// The `Signer` implementation for `ExpandedSigningKey` uses the optional deterministic variant of ML-DSA, and
534/// only supports signing with an empty context string.  If you would like to include a context
535/// string, use the [`ExpandedSigningKey::sign_deterministic`] method.
536impl<P: MlDsaParams> Signer<Signature<P>> for ExpandedSigningKey<P> {
537    fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, Error> {
538        self.try_multipart_sign(&[msg])
539    }
540}
541
542/// The `Signer` implementation for `ExpandedSigningKey` uses the optional deterministic variant of ML-DSA, and
543/// only supports signing with an empty context string. If you would like to include a context
544/// string, use the [`ExpandedSigningKey::sign_deterministic`] method.
545impl<P: MlDsaParams> MultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
546    fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, Error> {
547        self.raw_sign_deterministic(msg, &[])
548    }
549}
550
551/// The `Signer` implementation for `ExpandedSigningKey` uses the optional deterministic variant of ML-DSA
552/// with a pre-computed µ, and only supports signing with an empty context string. If you would
553/// like to include a context string, use the [`ExpandedSigningKey::sign_mu_deterministic`] method.
554impl<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
555    fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
556        &self,
557        f: F,
558    ) -> Result<Signature<P>, Error> {
559        let mut mu = MuBuilder::new(&self.tr, &[]);
560        f(mu.as_mut())?;
561        let mu = mu.finish();
562
563        self.try_raw_sign_mu(&mu, &B32::default())
564    }
565}
566
567/// The [`signature::Keypair`] implementation for [`ExpandedSigningKey`] allows to derive a
568/// [`VerifyingKey`] from a bare `ExpandedSigningKey` (even in the absence of the original seed).
569impl<P: MlDsaParams> signature::Keypair for ExpandedSigningKey<P> {
570    type VerifyingKey = VerifyingKey<P>;
571
572    /// This is a utility function that is useful when importing the private key
573    /// from an external source which does not export the seed and does not
574    /// provide the precomputed public key associated with the private key
575    /// itself.
576    fn verifying_key(&self) -> Self::VerifyingKey {
577        let As1 = &self.A_hat * &self.s1_hat;
578        let t = &As1.ntt_inverse() + &self.s2;
579
580        /* Discard t0 */
581        let (t1, _) = t.power2round();
582
583        VerifyingKey::new(self.rho.clone(), t1, self.A_hat.clone(), None)
584    }
585}
586
587/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty
588/// context string. If you would like to include a context string, use the
589/// [`ExpandedSigningKey::sign_randomized`] method.
590#[cfg(feature = "rand_core")]
591impl<P: MlDsaParams> RandomizedSigner<Signature<P>> for ExpandedSigningKey<P> {
592    fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
593        &self,
594        rng: &mut R,
595        msg: &[u8],
596    ) -> Result<Signature<P>, Error> {
597        self.try_multipart_sign_with_rng(rng, &[msg])
598    }
599}
600
601/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty
602/// context string. If you would like to include a context string, use the
603/// [`ExpandedSigningKey::sign_randomized`] method.
604#[cfg(feature = "rand_core")]
605impl<P: MlDsaParams> RandomizedMultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
606    fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
607        &self,
608        rng: &mut R,
609        msg: &[&[u8]],
610    ) -> Result<Signature<P>, Error> {
611        self.raw_sign_randomized(msg, &[], rng)
612    }
613}
614
615/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty
616/// context string. If you would like to include a context string, use the
617/// [`ExpandedSigningKey::sign_mu_randomized`] method.
618#[cfg(feature = "rand_core")]
619impl<P: MlDsaParams> RandomizedDigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
620    fn try_sign_digest_with_rng<
621        R: TryCryptoRng + ?Sized,
622        F: Fn(&mut Shake256) -> Result<(), Error>,
623    >(
624        &self,
625        rng: &mut R,
626        f: F,
627    ) -> Result<Signature<P>, Error> {
628        let mut mu = MuBuilder::new(&self.tr, &[]);
629        f(mu.as_mut())?;
630        let mu = mu.finish();
631
632        self.sign_mu_randomized(&mu, rng)
633    }
634}
635
636impl<P: MlDsaParams> PartialEq for ExpandedSigningKey<P> {
637    fn eq(&self, other: &Self) -> bool {
638        self.ct_eq(other).into()
639    }
640}
641
642impl<P: MlDsaParams> CtEq for ExpandedSigningKey<P> {
643    fn ct_eq(&self, other: &Self) -> Choice {
644        self.rho
645            .ct_eq(&other.rho)
646            .and(self.K.ct_eq(&other.K))
647            .and(self.tr.ct_eq(&other.tr))
648            .and(self.s1.ct_eq(&other.s1))
649            .and(self.s2.ct_eq(&other.s2))
650            .and(self.t0.ct_eq(&other.t0))
651    }
652}
653
654impl<P: MlDsaParams> fmt::Debug for ExpandedSigningKey<P> {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        f.debug_struct("ExpandedSigningKey").finish_non_exhaustive()
657    }
658}
659
660impl<P: MlDsaParams> Drop for ExpandedSigningKey<P> {
661    fn drop(&mut self) {
662        #[cfg(feature = "zeroize")]
663        {
664            self.rho.zeroize();
665            self.K.zeroize();
666            self.tr.zeroize();
667            self.s1.zeroize();
668            self.s2.zeroize();
669            self.t0.zeroize();
670            self.s1_hat.zeroize();
671            self.s2_hat.zeroize();
672            self.t0_hat.zeroize();
673        }
674    }
675}
676
677#[cfg(feature = "zeroize")]
678impl<P: MlDsaParams> ZeroizeOnDrop for ExpandedSigningKey<P> {}
679
680/// Nonce bases for complete `ExpandMask` batches, without overflowing `u16` nonce.
681fn signing_nonces<P: MlDsaParams>() -> impl Iterator<Item = u16> {
682    (0..=u16::MAX - (P::L::U16 - 1)).step_by(P::L::USIZE)
683}
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688    use crate::{MlDsa44, MlDsa65, MlDsa87};
689
690    fn check_signing_nonces<P: MlDsaParams>(expected_last: u16, expected_count: usize) {
691        let mut count = 0;
692        let mut last = None;
693        for kappa in signing_nonces::<P>() {
694            assert_eq!(usize::from(kappa), count * P::L::USIZE);
695            assert!(kappa.checked_add(P::L::U16 - 1).is_some());
696            last = Some(kappa);
697            count += 1;
698        }
699        assert_eq!(last, Some(expected_last));
700        assert_eq!(count, expected_count);
701
702        // The next batch would exceed the nonce space.
703        assert!(u32::from(expected_last) + 2 * P::L::U32 - 1 > u32::from(u16::MAX));
704
705        // Exercise ExpandMask at the final permitted nonce base.
706        let _ = expand_mask::<P::L, P::Gamma1>(&B64::default(), expected_last);
707    }
708
709    #[test]
710    fn signing_nonce_bounds() {
711        check_signing_nonces::<MlDsa44>(65532, 16384);
712        check_signing_nonces::<MlDsa65>(65530, 13107);
713        check_signing_nonces::<MlDsa87>(65527, 9362);
714    }
715}