diff --git a/Cargo.toml b/Cargo.toml index 9a42eee..a97ed3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/ed25519/src/pkcs8.rs b/ed25519/src/pkcs8.rs index 8fdea6c..8cbfa07 100644 --- a/ed25519/src/pkcs8.rs +++ b/ed25519/src/pkcs8.rs @@ -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 { // 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); diff --git a/ed448/src/pkcs8.rs b/ed448/src/pkcs8.rs index bde4682..1158504 100644 --- a/ed448/src/pkcs8.rs +++ b/ed448/src/pkcs8.rs @@ -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 { // 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); diff --git a/ml-dsa/Cargo.toml b/ml-dsa/Cargo.toml index 76fb641..85d7046 100644 --- a/ml-dsa/Cargo.toml +++ b/ml-dsa/Cargo.toml @@ -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 diff --git a/ml-dsa/benches/ml_dsa.rs b/ml-dsa/benches/ml_dsa.rs index 9babe0b..0a17bb7 100644 --- a/ml-dsa/benches/ml_dsa.rs +++ b/ml-dsa/benches/ml_dsa.rs @@ -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(rng: &mut R) -> Array { let mut val = Array::::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::::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::::decode(&vk_bytes); let sig = Signature::::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); - }) + }); }); } diff --git a/ml-dsa/src/algebra.rs b/ml-dsa/src/algebra.rs index 68bbc98..81315ec 100644 --- a/ml-dsa/src/algebra.rs +++ b/ml-dsa/src/algebra.rs @@ -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 AlgebraExt for Vector { } 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) { diff --git a/ml-dsa/src/hint.rs b/ml-dsa/src/hint.rs index c03ad1f..5141bb0 100644 --- a/ml-dsa/src/hint.rs +++ b/ml-dsa/src/hint.rs @@ -130,7 +130,7 @@ where let cuts: Array = cuts.iter().map(|x| usize::from(*x)).collect(); let indices: Array = 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]) diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 7bc4bf7..5a5e606 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -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 DigestVerifier> for VerifyingKey

} /// `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 { diff --git a/ml-dsa/src/param.rs b/ml-dsa/src/param.rs index 2b00bcc..d502995 100644 --- a/ml-dsa/src/param.rs +++ b/ml-dsa/src/param.rs @@ -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, diff --git a/ml-dsa/src/pkcs8.rs b/ml-dsa/src/pkcs8.rs index dca9ab5..11b1c10 100644 --- a/ml-dsa/src/pkcs8.rs +++ b/ml-dsa/src/pkcs8.rs @@ -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>, { - fn to_pkcs8_der(&self) -> ::pkcs8::Result { + fn to_pkcs8_der(&self) -> ::pkcs8::Result { let seed_der = SeedString { tag_mode: TagMode::Implicit, tag_number: SEED_TAG_NUMBER, @@ -174,7 +174,7 @@ where P: MlDsaParams, P: AssociatedAlgorithmIdentifier>, { - fn to_public_key_der(&self) -> spki::Result { + fn to_public_key_der(&self) -> spki::Result { let public_key = self.encode(); let subject_public_key = BitStringRef::new(0, &public_key)?; diff --git a/ml-dsa/tests/key-gen.rs b/ml-dsa/tests/key-gen.rs index 6e22fbe..7961f68 100644 --- a/ml-dsa/tests/key-gen.rs +++ b/ml-dsa/tests/key-gen.rs @@ -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}; diff --git a/ml-dsa/tests/pkcs8.rs b/ml-dsa/tests/pkcs8.rs index 8b8df5e..4faae86 100644 --- a/ml-dsa/tests/pkcs8.rs +++ b/ml-dsa/tests/pkcs8.rs @@ -1,3 +1,5 @@ +//! PKCS#8 tests (i.e. private key serialization) + #![cfg(all(feature = "pkcs8", feature = "alloc"))] use core::ops::Deref; diff --git a/ml-dsa/tests/sig-gen.rs b/ml-dsa/tests/sig-gen.rs index 73b3957..175af43 100644 --- a/ml-dsa/tests/sig-gen.rs +++ b/ml-dsa/tests/sig-gen.rs @@ -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] diff --git a/ml-dsa/tests/sig-ver.rs b/ml-dsa/tests/sig-ver.rs index 1c21cda..3c635b2 100644 --- a/ml-dsa/tests/sig-ver.rs +++ b/ml-dsa/tests/sig-ver.rs @@ -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]