From 0536caa0db44df446130b3d3cc4d46336b3c8d5f Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 1 Jul 2026 20:54:27 -0600 Subject: [PATCH] ecdsa: fix `clippy::missing_errors_doc` lints Adds the docs --- ecdsa/src/der.rs | 6 +++-- ecdsa/src/hazmat.rs | 19 +++++++++------- ecdsa/src/lib.rs | 51 ++++++++++++++++++++++++++++++------------ ecdsa/src/recovery.rs | 38 ++++++++++++++++++------------- ecdsa/src/signing.rs | 15 +++++++++---- ecdsa/src/verifying.rs | 12 ++++++++-- 6 files changed, 95 insertions(+), 46 deletions(-) diff --git a/ecdsa/src/der.rs b/ecdsa/src/der.rs index 1f110aa0..ae797566 100644 --- a/ecdsa/src/der.rs +++ b/ecdsa/src/der.rs @@ -84,6 +84,9 @@ where as Add>::Output: Add + ArraySize, { /// Parse signature from DER-encoded bytes. + /// + /// # Errors + /// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature. pub fn from_bytes(input: &[u8]) -> Result { let SignatureRef { r, s } = SignatureRef::from_der(input).map_err(|_| Error::new())?; @@ -110,8 +113,7 @@ where }) } - /// Create an ASN.1 DER encoded signature from big endian `r` and `s` scalar - /// components. + /// Create an ASN.1 DER encoded signature from big endian `r` and `s` scalar components. pub(crate) fn from_components(r: &[u8], s: &[u8]) -> der::Result { let sig = SignatureRef { r: UintRef::new(r)?, diff --git a/ecdsa/src/hazmat.rs b/ecdsa/src/hazmat.rs index 5d7464b9..f61ce803 100644 --- a/ecdsa/src/hazmat.rs +++ b/ecdsa/src/hazmat.rs @@ -33,8 +33,7 @@ use digest::{Digest, block_api::BlockSizeUser}; /// /// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!! /// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!! -/// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY -/// SECURE DIGEST ALGORITHM!!! +/// - `z`: message digest to sign. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM! /// /// # Low-S Normalization /// @@ -58,6 +57,7 @@ pub fn sign_prehashed( where C: EcdsaCurve + CurveArithmetic, { + // Reduce message hash into an element of the scalar field for `C`. let z = bytes2scalar::(z); // Compute scalar inversion of 𝑘. @@ -128,19 +128,22 @@ where /// Accepts the following arguments: /// /// - `q`: public key with which to verify the signature. -/// - `z`: message digest to be verified. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST -/// ALGORITHM!!! -/// - `sig`: signature to be verified against the key and message. -pub fn verify_prehashed(q: &ProjectivePoint, z: &[u8], sig: &Signature) -> Result<()> +/// - `z`: message digest to verify. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM! +/// - `signature`: purported signature to verify against the key and message. +/// +/// # Errors +/// Returns [`Error`] if the signature failed to verify. +pub fn verify_prehashed(q: &ProjectivePoint, z: &[u8], signature: &Signature) -> Result<()> where C: EcdsaCurve + CurveArithmetic, { - if C::NORMALIZE_S && sig.s().is_high().into() { + let (r, s) = signature.split_scalars(); + + if C::NORMALIZE_S && s.is_high().into() { return Err(Error::new()); } let z = bytes2scalar::(z); - let (r, s) = sig.split_scalars(); let s_inv = *s.invert_vartime(); let u1 = z * s_inv; let u2 = *r * s_inv; diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 9e4c8c27..c3b2e854 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -5,7 +5,6 @@ html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" )] -#![allow(clippy::missing_errors_doc, reason = "TODO")] //! ## `serde` support //! @@ -192,8 +191,8 @@ impl Signature where C: EcdsaCurve, { - /// Parse a signature from fixed-width bytes, i.e. 2 * the size of - /// [`FieldBytes`] for a particular curve. + /// Parse a signature from fixed-width bytes, i.e. 2 * the size of [`FieldBytes`] for a + /// particular curve. /// /// # Errors /// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big @@ -206,6 +205,10 @@ where } /// Parse a signature from a byte slice. + /// + /// # Errors + /// Returns [`Error`] in the event the signature is not the expected size, i.e. 2 * the size of + /// [`FieldBytes`] for a particular curve. pub fn from_slice(slice: &[u8]) -> Result { <&SignatureBytes>::try_from(slice) .map_err(|_| Error::new()) @@ -213,6 +216,9 @@ where } /// Parse a signature from ASN.1 DER. + /// + /// # Errors + /// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature. #[cfg(feature = "der")] pub fn from_der(bytes: &[u8]) -> Result where @@ -226,7 +232,8 @@ where /// which comprise the signature. /// /// # Errors - /// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big endian integer. + /// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big + /// endian integer. pub fn from_scalars(r: impl Into>, s: impl Into>) -> Result { let r = ScalarValue::from_slice(&r.into()).map_err(|_| Error::new())?; let s = ScalarValue::from_slice(&s.into()).map_err(|_| Error::new())?; @@ -515,15 +522,12 @@ where /// OID must begin with `1.2.840.10045.4`, the [RFC5758] OID prefix for ECDSA variants. /// /// [RFC5758]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2 + /// + /// # Errors + /// Returns [`Error`] if `oid` does not start with `1.2.840.10045.4`. pub fn new(signature: Signature, oid: ObjectIdentifier) -> Result { - // TODO(tarcieri): use `ObjectIdentifier::starts_with` - for (arc1, arc2) in ObjectIdentifier::new_unwrap("1.2.840.10045.4.3") - .arcs() - .zip(oid.arcs()) - { - if arc1 != arc2 { - return Err(Error::new()); - } + if !oid.starts_with(ObjectIdentifier::new_unwrap("1.2.840.10045.4")) { + return Err(Error::new()); } Ok(Self { signature, oid }) @@ -531,10 +535,13 @@ where /// Create a new signature, determining the OID from the given digest. /// - /// Supports SHA-2 family digests as enumerated in [RFC5758 § 3.2], i.e. - /// SHA-224, SHA-256, SHA-384, or SHA-512. + /// Supports SHA-2 family digests as enumerated in [RFC5758 § 3.2], i.e. SHA-224, SHA-256, + /// SHA-384, or SHA-512. /// /// [RFC5758 § 3.2]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2 + /// + /// # Errors + /// Returns [`Error`] if the [`AssociatedOid`] for `D` is not one from the SHA2 family. pub fn new_with_digest(signature: Signature) -> Result where D: AssociatedOid + Digest, @@ -544,6 +551,10 @@ where } /// Parse a signature from fixed-with bytes. + /// + /// # Errors + /// Returns [`Error`] if [`Signature`] fails to parse, or if `D` is not a valid digest. + /// See [`SignatureWithOid::new_with_digest`] documentation. pub fn from_bytes_with_digest(bytes: &SignatureBytes) -> Result where D: AssociatedOid + Digest, @@ -552,6 +563,10 @@ where } /// Parse a signature from a byte slice. + /// + /// # Errors + /// Returns [`Error`] if [`Signature`] fails to parse, or if `D` is not a valid digest. + /// See [`SignatureWithOid::new_with_digest`] documentation. pub fn from_slice_with_digest(slice: &[u8]) -> Result where D: AssociatedOid + Digest, @@ -560,6 +575,10 @@ where } /// Parse a signature from ASN.1 DER and associate the given digest's OID with it. + /// + /// # Errors + /// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature, + /// or if `D` is not a valid digest. #[cfg(feature = "der")] pub fn from_der_with_digest(der_bytes: &[u8]) -> Result where @@ -571,6 +590,10 @@ where } /// Parse a signature from ASN.1 DER and associate the given OID with it. + /// + /// # Errors + /// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature, + /// or if `D` is not a valid digest. #[cfg(feature = "der")] pub fn from_der_with_oid(der_bytes: &[u8], oid: ObjectIdentifier) -> Result where diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index 2bfba2df..c0f42f7c 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -183,13 +183,16 @@ where { /// Sign the given message prehash, using the given rng for the RFC6979 Section 3.6 "additional /// data", returning a signature and recovery ID. + /// + /// # Errors + /// pub fn sign_prehash_recoverable_with_rng( &self, rng: &mut R, prehash: &[u8], - ) -> Result<(Signature, RecoveryId)> { + ) -> core::result::Result<(Signature, RecoveryId), R::Error> { let mut ad = FieldBytes::::default(); - rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?; + rng.try_fill_bytes(&mut ad)?; Ok(sign_prehashed_rfc6979::( self.as_nonzero_scalar(), prehash, @@ -198,25 +201,18 @@ where } /// Sign the given message prehash, returning a signature and recovery ID. - pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> Result<(Signature, RecoveryId)> { - Ok(sign_prehashed_rfc6979::( - self.as_nonzero_scalar(), - prehash, - b"", - )) + pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> (Signature, RecoveryId) { + sign_prehashed_rfc6979::(self.as_nonzero_scalar(), prehash, b"") } /// Sign the given message digest, returning a signature and recovery ID. - pub fn sign_digest_recoverable(&self, msg_digest: D) -> Result<(Signature, RecoveryId)> - where - D: Digest, - { + pub fn sign_digest_recoverable(&self, msg_digest: D) -> (Signature, RecoveryId) { self.sign_prehash_recoverable(&msg_digest.finalize()) } /// Sign the given message, hashing it with the curve's default digest /// function, and returning a signature and recovery ID. - pub fn sign_recoverable(&self, msg: &[u8]) -> Result<(Signature, RecoveryId)> { + pub fn sign_recoverable(&self, msg: &[u8]) -> (Signature, RecoveryId) { self.sign_digest_recoverable(C::Digest::new_with_prefix(msg)) } } @@ -234,7 +230,7 @@ where ) -> Result<(Signature, RecoveryId)> { let mut digest = D::new(); f(&mut digest)?; - self.sign_digest_recoverable(digest) + Ok(self.sign_digest_recoverable(digest)) } } @@ -250,6 +246,7 @@ where prehash: &[u8], ) -> Result<(Signature, RecoveryId)> { self.sign_prehash_recoverable_with_rng(rng, prehash) + .map_err(|_| Error::new()) } } @@ -278,7 +275,7 @@ where Scalar: Invert>>, { fn sign_prehash(&self, prehash: &[u8]) -> Result<(Signature, RecoveryId)> { - self.sign_prehash_recoverable(prehash) + Ok(self.sign_prehash_recoverable(prehash)) } } @@ -303,7 +300,7 @@ where let mut digest = C::Digest::new(); msg.iter() .for_each(|slice| Update::update(&mut digest, slice)); - self.sign_digest_recoverable(digest) + Ok(self.sign_digest_recoverable(digest)) } } @@ -317,6 +314,9 @@ where /// Recover a [`VerifyingKey`] from the given message, signature, and [`RecoveryId`]. /// /// The message is first hashed using this curve's [`DigestAlgorithm`]. + /// + /// # Errors + /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. pub fn recover_from_msg( msg: &[u8], signature: &Signature, @@ -329,6 +329,9 @@ where } /// Recover a [`VerifyingKey`] from the given message [`Digest`], signature, and [`RecoveryId`]. + /// + /// # Errors + /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. pub fn recover_from_digest( msg_digest: D, signature: &Signature, @@ -353,6 +356,9 @@ where /// it in a system of linear equations that can cause the recovery function to output any public /// key the attacker wants. /// + /// + /// # Errors + /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. #[allow(non_snake_case)] pub fn recover_from_prehash( prehash: &[u8], diff --git a/ecdsa/src/signing.rs b/ecdsa/src/signing.rs index f9206416..dc5b8c1d 100644 --- a/ecdsa/src/signing.rs +++ b/ecdsa/src/signing.rs @@ -78,6 +78,9 @@ where C: EcdsaCurve + CurveArithmetic, { /// Initialize signing key from a raw scalar serialized as a byte array. + /// + /// # Errors + /// Returns an error if `bytes` is not a valid element of the scalar field for `C`. pub fn from_bytes(bytes: &FieldBytes) -> Result { SecretKey::::from_bytes(bytes) .map(Into::into) @@ -85,6 +88,10 @@ where } /// Initialize signing key from a raw scalar serialized as a byte slice. + /// + /// # Errors + /// Returns an error if `bytes` is not the length of [`FieldBytes`] for `C`, or if it is not + /// a valid element of the scalar field for `C`. pub fn from_slice(bytes: &[u8]) -> Result { SecretKey::::from_slice(bytes) .map(Into::into) @@ -98,11 +105,11 @@ where /// Borrow the secret [`NonZeroScalar`] value for this key. /// - /// # ⚠️ Warning - /// - /// This value is key material. + ///
+ /// Security Warning /// - /// Please treat it with the care it deserves! + /// This value is key material. Please treat it with the care it deserves! + ///
pub fn as_nonzero_scalar(&self) -> &NonZeroScalar { &self.secret_scalar } diff --git a/ecdsa/src/verifying.rs b/ecdsa/src/verifying.rs index 809d6bae..d42fb717 100644 --- a/ecdsa/src/verifying.rs +++ b/ecdsa/src/verifying.rs @@ -76,6 +76,11 @@ where FieldBytesSize: sec1::ModulusSize, { /// Initialize [`VerifyingKey`] from a SEC1-encoded public key. + /// + /// # Errors + /// Returns [`Error`] if `bytes` is not a valid SEC1 encoding of a compressed or uncompressed + /// curve point (i.e. it should begin with `0x02`, `0x03`, or `0x04`), or if + /// [`VerifyingKey::from_sec1_point`] returns an error. pub fn from_sec1_bytes(bytes: &[u8]) -> Result { PublicKey::from_sec1_bytes(bytes) .map(|pk| Self { inner: pk }) @@ -84,8 +89,8 @@ where /// Initialize [`VerifyingKey`] from an affine point. /// - /// Returns an [`Error`] if the given affine point is the additive identity - /// (a.k.a. point at infinity). + /// # Errors + /// Returns [`Error`] if the `affine` point is the additive identity (a.k.a. point at infinity). pub fn from_affine(affine: AffinePoint) -> Result { Ok(Self { inner: PublicKey::from_affine(affine).map_err(|_| Error::new())?, @@ -93,6 +98,9 @@ where } /// Initialize [`VerifyingKey`] from an [`Sec1Point`]. + /// + /// # Errors + /// Returns [`Error`] if `public_key` does not represent a valid elliptic curve point for `C`. pub fn from_sec1_point(public_key: &Sec1Point) -> Result { PublicKey::::from_sec1_point(public_key) .into_option()