ml-dsa: apply and fix workspace-level lints (#1338)

Applies the workspace-level config added in #1323 to this crate and
fixes any failures.
This commit is contained in:
Tony Arcieri
2026-05-07 21:14:26 -06:00
committed by GitHub
parent ebe040ee89
commit 0c7cf02d1b
14 changed files with 82 additions and 51 deletions
+2
View File
@@ -17,6 +17,7 @@ members = [
opt-level = 2
[workspace.lints.clippy]
as_conversions = "warn"
borrow_as_ptr = "warn"
cast_lossless = "warn"
cast_possible_truncation = "warn"
@@ -26,6 +27,7 @@ cast_sign_loss = "warn"
checked_conversions = "warn"
from_iter_instead_of_collect = "warn"
implicit_saturating_sub = "warn"
integer_division_remainder_used = "warn"
manual_assert = "warn"
map_unwrap_or = "warn"
missing_errors_doc = "warn"
+14 -14
View File
@@ -20,24 +20,24 @@ pub use pkcs8::{
};
#[cfg(feature = "alloc")]
pub use pkcs8::{EncodePrivateKey, spki::EncodePublicKey};
#[cfg(feature = "alloc")]
pub use pkcs8::der::{
Document, SecretDocument,
asn1::{BitStringRef, OctetStringRef},
pub use pkcs8::{
EncodePrivateKey,
der::{
Document, SecretDocument,
asn1::{BitStringRef, OctetStringRef},
},
spki::EncodePublicKey,
};
use crate::COMPONENT_SIZE;
use core::fmt;
#[cfg(feature = "pem")]
use core::str;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[cfg(feature = "zerocopy")]
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
/// Algorithm [`ObjectIdentifier`] for the Ed25519 digital signature algorithm
/// (`id-Ed25519`).
@@ -73,7 +73,7 @@ pub struct KeypairBytes {
/// Little endian serialization of an element of the Curve25519 scalar
/// field, prior to "clamping" (i.e. setting/clearing bits to ensure the
/// scalar is actually a valid field element)
pub secret_key: [u8; Self::BYTE_SIZE / 2],
pub secret_key: [u8; COMPONENT_SIZE],
/// Ed25519 public key (if available).
///
@@ -90,7 +90,7 @@ impl KeypairBytes {
#[allow(clippy::missing_panics_doc, reason = "MSRV TODO")]
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
// TODO(tarcieri): use `as_chunks` when MSRV is 1.88
let (sk, pk) = bytes.split_at(Self::BYTE_SIZE / 2);
let (sk, pk) = bytes.split_at(COMPONENT_SIZE);
Self {
secret_key: sk.try_into().expect("secret key size error"),
@@ -109,7 +109,7 @@ impl KeypairBytes {
pub fn to_bytes(&self) -> Option<[u8; Self::BYTE_SIZE]> {
if let Some(public_key) = &self.public_key {
let mut result = [0u8; Self::BYTE_SIZE];
let (sk, pk) = result.split_at_mut(Self::BYTE_SIZE / 2);
let (sk, pk) = result.split_at_mut(COMPONENT_SIZE);
sk.copy_from_slice(&self.secret_key);
pk.copy_from_slice(public_key.as_ref());
Some(result)
@@ -130,7 +130,7 @@ impl Drop for KeypairBytes {
impl EncodePrivateKey for KeypairBytes {
fn to_pkcs8_der(&self) -> Result<SecretDocument> {
// Serialize private key as nested OCTET STRING
let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
let mut private_key = [0u8; 2 + (COMPONENT_SIZE)];
private_key[0] = 0x04;
private_key[1] = 0x20;
private_key[2..].copy_from_slice(&self.secret_key);
+14 -12
View File
@@ -20,19 +20,21 @@ pub use pkcs8::{
};
#[cfg(feature = "alloc")]
pub use pkcs8::{EncodePrivateKey, spki::EncodePublicKey};
#[cfg(feature = "alloc")]
pub use pkcs8::der::{
Document, SecretDocument,
asn1::{BitStringRef, OctetStringRef},
pub use pkcs8::{
EncodePrivateKey,
der::{
Document, SecretDocument,
asn1::{BitStringRef, OctetStringRef},
},
spki::EncodePublicKey,
};
use crate::COMPONENT_SIZE;
use core::fmt;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
use core::fmt;
/// Algorithm [`ObjectIdentifier`] for the Ed448 digital signature algorithm
/// (`id-Ed448`).
///
@@ -67,7 +69,7 @@ pub struct KeypairBytes {
/// Little endian serialization of an element of the Curve448 scalar
/// field, prior to "clamping" (i.e. setting/clearing bits to ensure the
/// scalar is actually a valid field element)
pub secret_key: [u8; Self::BYTE_SIZE / 2],
pub secret_key: [u8; COMPONENT_SIZE],
/// Ed448 public key (if available).
///
@@ -84,7 +86,7 @@ impl KeypairBytes {
#[allow(clippy::missing_panics_doc, reason = "MSRV TODO")]
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
// TODO(tarcieri): use `as_chunks` when MSRV is 1.88
let (sk, pk) = bytes.split_at(Self::BYTE_SIZE / 2);
let (sk, pk) = bytes.split_at(COMPONENT_SIZE);
Self {
secret_key: sk.try_into().expect("secret key size error"),
@@ -103,7 +105,7 @@ impl KeypairBytes {
pub fn to_bytes(&self) -> Option<[u8; Self::BYTE_SIZE]> {
if let Some(public_key) = &self.public_key {
let mut result = [0u8; Self::BYTE_SIZE];
let (sk, pk) = result.split_at_mut(Self::BYTE_SIZE / 2);
let (sk, pk) = result.split_at_mut(COMPONENT_SIZE);
sk.copy_from_slice(&self.secret_key);
pk.copy_from_slice(public_key.as_ref());
Some(result)
@@ -124,7 +126,7 @@ impl Drop for KeypairBytes {
impl EncodePrivateKey for KeypairBytes {
fn to_pkcs8_der(&self) -> Result<SecretDocument> {
// Serialize private key as nested OCTET STRING
let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
let mut private_key = [0u8; 2 + COMPONENT_SIZE];
private_key[0] = 0x04;
private_key[1] = 0x39;
private_key[2..].copy_from_slice(&self.secret_key);
+6
View File
@@ -57,3 +57,9 @@ serde_json = "1.0.132"
[[bench]]
name = "ml_dsa"
harness = false
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true
+11 -4
View File
@@ -1,3 +1,8 @@
//! ML-DSA benchmarks.
#![allow(clippy::unwrap_used, reason = "benchmarks")]
#![allow(missing_docs, reason = "benchmarks")]
use criterion::{Criterion, criterion_group, criterion_main};
use getrandom::SysRng;
use hybrid_array::{Array, ArraySize};
@@ -6,12 +11,14 @@ use ml_dsa::{
};
use rand_core::{CryptoRng, UnwrapErr};
/// Create a random array of the given length.
pub fn rand<L: ArraySize, R: CryptoRng + ?Sized>(rng: &mut R) -> Array<u8, L> {
let mut val = Array::<u8, L>::default();
rng.fill_bytes(&mut val);
val
}
/// ML-DSA benchmarks.
#[allow(deprecated)] // TODO(tarcieri): stop using expanded signing keys
fn criterion_benchmark(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
@@ -34,7 +41,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let kp = MlDsa65::from_seed(&xi);
let _sk_bytes = kp.signing_key().to_expanded();
let _vk_bytes = kp.verifying_key().encode();
})
});
});
// Signing
@@ -42,7 +49,7 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
let sk = ExpandedSigningKey::<MlDsa65>::from_expanded(&sk_bytes);
let _sig = sk.sign_deterministic(&m, &ctx);
})
});
});
// Verifying
@@ -51,7 +58,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let vk = VerifyingKey::<MlDsa65>::decode(&vk_bytes);
let sig = Signature::<MlDsa65>::decode(&sig_bytes).unwrap();
let _ver = vk.verify_with_context(&m, &ctx, &sig);
})
});
});
// Round trip
@@ -60,7 +67,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let kp = MlDsa65::from_seed(&xi);
let sig = kp.signing_key().sign_deterministic(&m, &ctx).unwrap();
let _ver = kp.verifying_key().verify_with_context(&m, &ctx, &sig);
})
});
});
}
+11 -3
View File
@@ -69,7 +69,7 @@ pub(crate) trait ConstantTimeDiv: Unsigned {
// This gives us floor(x / M) for x < 2^SHIFT / MULTIPLIER * M
let x64 = u64::from(x);
let quotient = (x64 * Self::CT_DIV_MULTIPLIER) >> Self::CT_DIV_SHIFT;
// SAFETY: quotient is guaranteed to fit in u32 because:
// Quotient is guaranteed to fit in u32 because:
// - x < Q (~2^23), so quotient = x / M < x < 2^23 < 2^32
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
let result = quotient as u32;
@@ -183,7 +183,11 @@ impl AlgebraExt for Polynomial {
}
fn infinity_norm(&self) -> u32 {
self.0.iter().map(AlgebraExt::infinity_norm).max().unwrap()
self.0
.iter()
.map(AlgebraExt::infinity_norm)
.max()
.expect("should have a maximum")
}
fn power2round(&self) -> (Self, Self) {
@@ -222,7 +226,11 @@ impl<K: ArraySize> AlgebraExt for Vector<K> {
}
fn infinity_norm(&self) -> u32 {
self.0.iter().map(AlgebraExt::infinity_norm).max().unwrap()
self.0
.iter()
.map(AlgebraExt::infinity_norm)
.max()
.expect("should have a maximum")
}
fn power2round(&self) -> (Self, Self) {
+1 -1
View File
@@ -130,7 +130,7 @@ where
let cuts: Array<usize, P::K> = cuts.iter().map(|x| usize::from(*x)).collect();
let indices: Array<usize, P::Omega> = indices.iter().map(|x| usize::from(*x)).collect();
let max_cut: usize = cuts.iter().copied().max().unwrap();
let max_cut: usize = cuts.iter().copied().max().expect("should have a maximum");
// cuts must be monotonic but can repeat
if !cuts.windows(2).all(|w| w[0] <= w[1])
+3 -8
View File
@@ -5,15 +5,10 @@
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(clippy::pedantic)] // Be pedantic by default
#![warn(clippy::integer_division_remainder_used)] // Be judicious about using `/` and `%`
#![warn(clippy::as_conversions)] // Use proper conversions, not `as`
#![allow(non_snake_case)] // Allow notation matching the spec
#![allow(clippy::similar_names)] // Allow notation matching the spec
#![allow(clippy::many_single_char_names)] // Allow notation matching the spec
#![allow(clippy::clone_on_copy)] // Be explicit about moving data
#![deny(missing_docs)] // Require all public interfaces to be documented
#![warn(unreachable_pub)] // Prevent unexpected interface changes
//! # Quickstart
//!
@@ -877,7 +872,7 @@ impl<P: MlDsaParams> DigestVerifier<Shake256, Signature<P>> for VerifyingKey<P>
}
/// `MlDsa44` is the parameter set for security category 2.
#[derive(Default, Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct MlDsa44;
impl ParameterSet for MlDsa44 {
@@ -894,7 +889,7 @@ impl ParameterSet for MlDsa44 {
}
/// `MlDsa65` is the parameter set for security category 3.
#[derive(Default, Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct MlDsa65;
impl ParameterSet for MlDsa65 {
@@ -911,7 +906,7 @@ impl ParameterSet for MlDsa65 {
}
/// `MlDsa87` is the parameter set for security category 5.
#[derive(Default, Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct MlDsa87;
impl ParameterSet for MlDsa87 {
+1 -1
View File
@@ -41,7 +41,7 @@ pub trait SamplingSize: ArraySize + Len {
}
/// Range of the uniform distribution to be used in sampling.
#[derive(Copy, Clone)]
#[derive(Clone, Copy, Debug)]
#[allow(unreachable_pub, reason = "MSRV 1.85 complains")]
pub enum Eta {
Two,
+5 -5
View File
@@ -99,13 +99,13 @@ where
.assert_algorithm_oid(P::ALGORITHM_IDENTIFIER.oid)?;
let mut reader = der::SliceReader::new(private_key_info.private_key.as_bytes())?;
let seed_string = SeedString::decode_implicit(&mut reader, SEED_TAG_NUMBER)?
.ok_or(pkcs8::KeyError::Invalid)?;
let seed_string =
SeedString::decode_implicit(&mut reader, SEED_TAG_NUMBER)?.ok_or(KeyError::Invalid)?;
let seed = seed_string
.value
.as_bytes()
.try_into()
.map_err(|_| pkcs8::KeyError::Invalid)?;
.map_err(|_| KeyError::Invalid)?;
reader.finish()?;
Ok(P::from_seed(&seed))
@@ -118,7 +118,7 @@ where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
fn to_pkcs8_der(&self) -> ::pkcs8::Result<der::SecretDocument> {
fn to_pkcs8_der(&self) -> ::pkcs8::Result<SecretDocument> {
let seed_der = SeedString {
tag_mode: TagMode::Implicit,
tag_number: SEED_TAG_NUMBER,
@@ -174,7 +174,7 @@ where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
fn to_public_key_der(&self) -> spki::Result<der::Document> {
fn to_public_key_der(&self) -> spki::Result<Document> {
let public_key = self.encode();
let subject_public_key = BitStringRef::new(0, &public_key)?;
+4 -1
View File
@@ -1,6 +1,9 @@
use ml_dsa::*;
//! Key generation tests.
#![allow(clippy::unwrap_used, reason = "tests")]
use hybrid_array::Array;
use ml_dsa::*;
use signature::Keypair;
use std::{fs::read_to_string, path::PathBuf};
+2
View File
@@ -1,3 +1,5 @@
//! PKCS#8 tests (i.e. private key serialization)
#![cfg(all(feature = "pkcs8", feature = "alloc"))]
use core::ops::Deref;
+4 -1
View File
@@ -1,5 +1,8 @@
use ml_dsa::*;
//! Signature generation tests.
#![allow(clippy::unwrap_used, reason = "tests")]
use ml_dsa::*;
use std::{fs::read_to_string, path::PathBuf};
#[test]
+4 -1
View File
@@ -1,5 +1,8 @@
use ml_dsa::*;
//! Signature verification tests.
#![allow(clippy::unwrap_used, reason = "tests")]
use ml_dsa::*;
use std::{fs::read_to_string, path::PathBuf};
#[test]