crypto: Remove error conversion From impls
What changed, and why it matters
This commit removes automatic error-conversion shortcuts in the crypto module of the rust-bitcoin library. It does not fix a bug or vulnerability; it is a deliberate API cleanup to avoid promising too much to future users. Existing code that relied on the removed conversions will need minor updates, but runtime behavior is unchanged.
No security action required. Downstream users should update code that depended on the removed `From` impls to use explicit `map_err` or matching constructors when upgrading rust-bitcoin.
Security signals we found
No security-relevant signal present
API-breaking change only
Explicit error mapping preserves identical runtime behavior
Evidence from the diff
The patch deletes From<BarError> for FooError implementations in crypto/src/ecdsa.rs and crypto/src/key.rs, replacing implicit ? conversions with explicit map_err calls. This is a breaking public API change in a pre-1.0 crate, intended to preserve future flexibility in error-type design. No security flaw is corrected; the conversions are simply made explicit at call sites.
Changed components
crypto/src/ecdsa.rscrypto/src/key.rsPublic error types: DecodeError, ParseSignatureError, FromSliceError, FromWifError, ParsePublicKeyError, ParseFullPublicKeyErrorInspect captured patch +23 / −65
diff --git a/crypto/src/ecdsa.rs b/crypto/src/ecdsa.rs
index c5c90969..3fa39735 100644
--- a/crypto/src/ecdsa.rs
+++ b/crypto/src/ecdsa.rs
@@ -51,7 +51,8 @@ impl Signature {
/// * [`DecodeError::Secp256k1`] if the slice cannot be decoded to an ECDSA signature.
pub fn from_slice(sl: &[u8]) -> Result<Self, DecodeError> {
let (sighash_type, sig) = sl.split_last().ok_or(DecodeError::EmptySignature)?;
- let sighash_type = EcdsaSighashType::from_standard(u32::from(*sighash_type))?;
+ let sighash_type = EcdsaSighashType::from_standard(u32::from(*sighash_type))
+ .map_err(DecodeError::SighashType)?;
let signature =
secp256k1::ecdsa::Signature::from_der(sig).map_err(DecodeError::Secp256k1)?;
Ok(Self { signature, sighash_type })
@@ -104,8 +105,8 @@ impl FromStr for Signature {
type Err = ParseSignatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let bytes = hex::decode_to_vec(s)?;
- Ok(Self::from_slice(&bytes)?)
+ let bytes = hex::decode_to_vec(s).map_err(ParseSignatureError::Hex)?;
+ Self::from_slice(&bytes).map_err(ParseSignatureError::Decode)
}
}
@@ -301,14 +302,6 @@ pub mod error {
}
}
- impl From<secp256k1::Error> for DecodeError {
- fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
- }
-
- impl From<NonStandardSighashTypeError> for DecodeError {
- fn from(e: NonStandardSighashTypeError) -> Self { Self::SighashType(e) }
- }
-
/// Error encountered while parsing an ECDSA signature from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -341,14 +334,6 @@ pub mod error {
}
}
}
-
- impl From<hex::DecodeVariableLengthBytesError> for ParseSignatureError {
- fn from(e: hex::DecodeVariableLengthBytesError) -> Self { Self::Hex(e) }
- }
-
- impl From<DecodeError> for ParseSignatureError {
- fn from(e: DecodeError) -> Self { Self::Decode(e) }
- }
}
#[cfg(feature = "arbitrary")]
diff --git a/crypto/src/key.rs b/crypto/src/key.rs
index 5be6c7d9..53403c70 100644
--- a/crypto/src/key.rs
+++ b/crypto/src/key.rs
@@ -718,9 +718,10 @@ impl LegacyPublicKey {
(_, byte) => return Err(FromSliceError::InvalidKeyPrefix(byte)),
}
+ let secp_key = secp256k1::PublicKey::from_slice(data).map_err(FromSliceError::Secp256k1)?;
Ok(match compressed {
- true => Self::from_secp(secp256k1::PublicKey::from_slice(data)?),
- false => Self::from_secp_uncompressed(secp256k1::PublicKey::from_slice(data)?),
+ true => Self::from_secp(secp_key),
+ false => Self::from_secp_uncompressed(secp_key),
})
}
@@ -779,7 +780,7 @@ impl FromStr for LegacyPublicKey {
DecodeFixedLengthBytesError::InvalidLength(_) =>
unreachable!("length checked already"),
})?;
- Ok(Self::from_slice(&bytes)?)
+ Self::from_slice(&bytes).map_err(ParsePublicKeyError::Encoding)
}
130 => {
let bytes = hex::decode_to_array::<65>(s).map_err(|e| match e {
@@ -788,7 +789,7 @@ impl FromStr for LegacyPublicKey {
DecodeFixedLengthBytesError::InvalidLength(_) =>
unreachable!("length checked already"),
})?;
- Ok(Self::from_slice(&bytes)?)
+ Self::from_slice(&bytes).map_err(ParsePublicKeyError::Encoding)
}
len => Err(ParsePublicKeyError::InvalidHexLength(len)),
}
@@ -930,7 +931,8 @@ impl FromStr for FullPublicKey {
type Err = ParseFullPublicKeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- Self::from_bytes(hex::decode_to_array::<33>(s)?).map_err(Into::into)
+ Self::from_bytes(hex::decode_to_array::<33>(s).map_err(ParseFullPublicKeyError::Hex)?)
+ .map_err(ParseFullPublicKeyError::Secp256k1)
}
}
@@ -1123,18 +1125,22 @@ impl WifKey {
/// * [`FromWifError::InvalidAddressVersion`] if the network version byte is not main or testnet.
/// * [`FromWifError::Secp256k1`] if the bytes are not representative of a valid private key.
pub fn from_wif(wif: &str) -> Result<Self, FromWifError> {
- let data = base58::decode_check(wif)?;
+ let data = base58::decode_check(wif).map_err(FromWifError::Base58)?;
let (compressed, data) = if let Ok(data) = <&[u8; 33]>::try_from(&*data) {
(false, data)
} else if let Ok(data) = <&[u8; 34]>::try_from(&*data) {
let (compressed_flag, data) = data.split_last::<33>();
if *compressed_flag != 1 {
- return Err(InvalidWifCompressionFlagError { invalid: *compressed_flag }.into());
+ return Err(FromWifError::InvalidWifCompressionFlag(
+ InvalidWifCompressionFlagError { invalid: *compressed_flag },
+ ));
}
(true, data)
} else {
- return Err(InvalidBase58PayloadLengthError { length: data.len() }.into());
+ return Err(FromWifError::InvalidBase58PayloadLength(
+ InvalidBase58PayloadLengthError { length: data.len() },
+ ));
};
let (network, key) = data.split_first();
@@ -1142,11 +1148,14 @@ impl WifKey {
128 => NetworkKind::Main,
239 => NetworkKind::Test,
invalid => {
- return Err(InvalidAddressVersionError { invalid }.into());
+ return Err(FromWifError::InvalidAddressVersion(InvalidAddressVersionError {
+ invalid,
+ }));
}
};
- let sec_key = secp256k1::SecretKey::from_secret_bytes(*key)?;
+ let sec_key =
+ secp256k1::SecretKey::from_secret_bytes(*key).map_err(FromWifError::Secp256k1)?;
let priv_key = match compressed {
true => PrivateKey::from_secp(sec_key),
false => PrivateKey::from_secp_uncompressed(sec_key),
@@ -1481,10 +1490,6 @@ pub mod error {
}
}
- impl From<secp256k1::Error> for FromSliceError {
- fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
- }
-
/// Error generated from WIF key format.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -1533,26 +1538,6 @@ pub mod error {
}
}
- impl From<base58::Error> for FromWifError {
- fn from(e: base58::Error) -> Self { Self::Base58(e) }
- }
-
- impl From<secp256k1::Error> for FromWifError {
- fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
- }
-
- impl From<InvalidBase58PayloadLengthError> for FromWifError {
- fn from(e: InvalidBase58PayloadLengthError) -> Self { Self::InvalidBase58PayloadLength(e) }
- }
-
- impl From<InvalidAddressVersionError> for FromWifError {
- fn from(e: InvalidAddressVersionError) -> Self { Self::InvalidAddressVersion(e) }
- }
-
- impl From<InvalidWifCompressionFlagError> for FromWifError {
- fn from(e: InvalidWifCompressionFlagError) -> Self { Self::InvalidWifCompressionFlag(e) }
- }
-
/// Error returned while constructing a [`Keypair`] from string.
///
/// [`Keypair`]: super::Keypair
@@ -1611,10 +1596,6 @@ pub mod error {
}
}
- impl From<FromSliceError> for ParsePublicKeyError {
- fn from(e: FromSliceError) -> Self { Self::Encoding(e) }
- }
-
/// Error returned when parsing a [`FullPublicKey`] from a string.
///
/// [`FullPublicKey`]: super::FullPublicKey
@@ -1649,14 +1630,6 @@ pub mod error {
}
}
- impl From<secp256k1::Error> for ParseFullPublicKeyError {
- fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
- }
-
- impl From<hex::DecodeFixedLengthBytesError> for ParseFullPublicKeyError {
- fn from(e: hex::DecodeFixedLengthBytesError) -> Self { Self::Hex(e) }
- }
-
/// SegWit public keys must always be compressed.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
Why this scored 19/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.