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