Introduce errors for byte parsing and verification
What changed, and why it matters
This commit is a routine code-quality refactor. It introduces new, more specific Rust error types for parsing keys and signatures, replacing a generic underlying library error type. There is no change to cryptographic behavior, validation logic, or security boundaries. It only changes what kind of error message callers receive when parsing fails.
No security action required. Treat as normal API cleanup. Reviewers may want to confirm downstream call sites are updated to use the new error types, but this is a compatibility/API concern, not a security fix.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch adds new error structs (InvalidPublicKeyError, FromSecretBytesError, VerifyError, InvalidDerError) and re-exports them in rust-bitcoin’s crypto crate. The stated goal is to hide the secp256k1 error type and allow richer error variants in the future. The diff shows only type/Display/Error trait boilerplate; no parsing, verification, or arithmetic code is modified.
Changed components
crypto/src/key.rscrypto/src/ecdsa.rsInspect captured patch +110 / −3
diff --git a/crypto/src/ecdsa.rs b/crypto/src/ecdsa.rs
index 7668859f..b1f04351 100644
--- a/crypto/src/ecdsa.rs
+++ b/crypto/src/ecdsa.rs
@@ -32,7 +32,7 @@ use crate::sighash::EcdsaSighashType;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(no_inline)]
-pub use self::error::DecodeError;
+pub use self::error::{DecodeError, InvalidDerError};
#[cfg(feature = "hex")]
#[doc(no_inline)]
pub use self::error::ParseSignatureError;
@@ -314,6 +314,27 @@ pub mod error {
}
}
+ /// The DER encoding of an ECDSA signature is not valid.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub struct InvalidDerError;
+
+ impl From<Infallible> for InvalidDerError {
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for InvalidDerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid DER encoding") }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for InvalidDerError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ let Self {} = self;
+ None
+ }
+ }
+
/// Error encountered while parsing an ECDSA signature from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
diff --git a/crypto/src/key.rs b/crypto/src/key.rs
index e0a6ff36..eddee3b5 100644
--- a/crypto/src/key.rs
+++ b/crypto/src/key.rs
@@ -36,8 +36,9 @@ use crate::hex::{self, DecodeFixedLengthBytesError};
pub use secp256k1::{constants, Parity, Verification};
#[doc(no_inline)]
pub use self::error::{
- FromSliceError, InvalidAddressVersionError, InvalidBase58PayloadLengthError, ParseKeypairError,
- ParseXOnlyPublicKeyError, TweakXOnlyPublicKeyError, UncompressedPublicKeyError,
+ FromSecretBytesError, FromSliceError, InvalidAddressVersionError,
+ InvalidBase58PayloadLengthError, InvalidPublicKeyError, ParseKeypairError,
+ ParseXOnlyPublicKeyError, TweakXOnlyPublicKeyError, UncompressedPublicKeyError, VerifyError,
};
#[cfg(feature = "alloc")]
#[doc(no_inline)]
@@ -1981,6 +1982,91 @@ pub mod error {
}
}
}
+
+ /// The bytes do not represent a valid secp256k1 public key.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub struct InvalidPublicKeyError;
+
+ impl From<Infallible> for InvalidPublicKeyError {
+ #[inline]
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for InvalidPublicKeyError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid public key") }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for InvalidPublicKeyError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ let Self {} = self;
+ None
+ }
+ }
+
+ /// Error that can occur when parsing a [`PrivateKey`] from a byte array.
+ ///
+ /// [`PrivateKey`]: super::PrivateKey
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct FromSecretBytesError(pub(super) FromSecretBytesErrorInner);
+
+ impl From<Infallible> for FromSecretBytesError {
+ #[inline]
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for FromSecretBytesError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ FromSecretBytesErrorInner::InvalidSecretKey => write!(f, "invalid secret key"),
+ }
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for FromSecretBytesError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self.0 {
+ FromSecretBytesErrorInner::InvalidSecretKey => None,
+ }
+ }
+ }
+
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub(super) enum FromSecretBytesErrorInner {
+ /// The bytes represent an invalid secp256k1 secret key.
+ InvalidSecretKey,
+ }
+
+ /// Signature verification failed for the given message and key.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub struct VerifyError;
+
+ impl From<Infallible> for VerifyError {
+ #[inline]
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for VerifyError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "signature verification failed")
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for VerifyError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ let Self {} = self;
+ None
+ }
+ }
}
#[cfg(feature = "arbitrary")]
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.