From c3ded0c799efd3f267fa96e0068b82612ae04faa Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 9 May 2026 16:03:49 -0600 Subject: [PATCH] ml-dsa: use `MaybeBox` for `SigningKey` (#1345) Opportunistically stores the parts of `SigningKey` on the heap when the `alloc` feature is enabled, similar to what #1344 did for `VerifyingKey`. This also addresses #1308 by adding a `Drop` (and `ZeroizeOnDrop`) impl to `SigningKey` which clears the `Seed`. --- ml-dsa/src/signing.rs | 52 ++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/ml-dsa/src/signing.rs b/ml-dsa/src/signing.rs index 895f728..d9728d7 100644 --- a/ml-dsa/src/signing.rs +++ b/ml-dsa/src/signing.rs @@ -37,14 +37,14 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Clone)] pub struct SigningKey { /// The expanded form of the signing key. - expanded_key: ExpandedSigningKey

, + expanded_key: MaybeBox>, /// The seed this signing key was derived from - seed: Seed, + seed: MaybeBox, } impl SigningKey

{ - /// Deterministically generate a signing key pair from the specified seed + /// Deterministically generate a signing key pair from the specified [`Seed`]. /// /// This method reflects the `ML-DSA.KeyGen_internal` algorithm from FIPS 204 (Algorithm 6). #[must_use] @@ -76,8 +76,8 @@ impl SigningKey

{ let signing_key = ExpandedSigningKey::new(rho, K, tr, s1, s2, t0, A_hat); SigningKey { - expanded_key: signing_key, - seed: xi.clone(), + expanded_key: MaybeBox::new(signing_key), + seed: MaybeBox::new(xi.clone()), } } @@ -90,12 +90,14 @@ impl SigningKey

{ /// This value is key material. Please treat it with care. /// #[inline] + #[must_use] pub fn to_seed(&self) -> Seed { - self.seed + *self.seed } /// The expanded form of the signing key. #[doc(hidden)] + #[must_use] pub fn expanded_key(&self) -> &ExpandedSigningKey

{ &self.expanded_key } @@ -180,6 +182,17 @@ impl CtEq for SigningKey

{ } } +impl Drop for SigningKey

{ + fn drop(&mut self) { + // NOTE: `expanded_key` has its own zeroizing `Drop` impl so we just need to clear `seed` + #[cfg(feature = "zeroize")] + self.seed.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for SigningKey

{} + /// An ML-DSA signing key #[derive(Clone)] pub struct ExpandedSigningKey { @@ -239,14 +252,15 @@ impl ExpandedSigningKey

{ Self::new(rho, K, tr, s1, s2, t0, A_hat) } - /// Deterministically generate a signing key from the specified seed. + /// Deterministically generate an expanded signing key from the specified seed. /// /// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a /// signing key. #[must_use] + #[inline] pub fn from_seed(seed: &Seed) -> Self { let kp = SigningKey::from_seed(seed); - kp.expanded_key + (*kp.expanded_key).clone() } /// This method reflects the ML-DSA.Sign_internal algorithm from FIPS 204. It does not @@ -590,18 +604,20 @@ impl fmt::Debug for ExpandedSigningKey

{ } } -#[cfg(feature = "zeroize")] impl Drop for ExpandedSigningKey

{ fn drop(&mut self) { - self.rho.zeroize(); - self.K.zeroize(); - self.tr.zeroize(); - self.s1.zeroize(); - self.s2.zeroize(); - self.t0.zeroize(); - self.s1_hat.zeroize(); - self.s2_hat.zeroize(); - self.t0_hat.zeroize(); + #[cfg(feature = "zeroize")] + { + self.rho.zeroize(); + self.K.zeroize(); + self.tr.zeroize(); + self.s1.zeroize(); + self.s2.zeroize(); + self.t0.zeroize(); + self.s1_hat.zeroize(); + self.s2_hat.zeroize(); + self.t0_hat.zeroize(); + } } }