Merge rust-bitcoin/rust-bitcoin#6765: addresses: Remove all non-`Infallible` `From` impls on error types
What changed, and why it matters
This commit removes automatic error-conversion shortcuts (Rust 'From' trait implementations) from address-parsing error types in the rust-bitcoin library. It is an API-cleanup change, not a fix for an exploitable bug. The code still reports the same errors; callers just have to convert them explicitly. There is no security vulnerability being patched here.
No security action required. Treat as a normal semver-aware API change; downstream users may need to update error-handling code when upgrading.
Security signals we found
No security signal present: change is an API-surface reduction, not a vulnerability fix
No mention of CVE, advisory, bug bounty, or exploit in commit message or PR description
No functional change to parsing logic or error semantics
Evidence from the diff
The patch deletes non-Infallible From impls on error enums in addresses/src/error.rs (FromScriptError, ParseError, Bech32Error, Base58Error) and updates call sites in addresses/src/lib.rs and bitcoin/src/address.rs to use explicit map_err constructors. The PR description frames this as reducing public-API commitment before the crate reaches 1.0. No memory-safety, cryptographic, or parsing-behavior flaw is corrected.
Changed components
addresses/src/error.rsaddresses/src/lib.rsbitcoin/src/address.rsInspect captured patch +20 / −66
### addresses/src/error.rs
@@ -50,14 +50,6 @@ impl std::error::Error for FromScriptError {
}
}
-impl From<witness_program::Error> for FromScriptError {
- fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
-}
-
-impl From<witness_version::InvalidWitnessVersionError> for FromScriptError {
- fn from(e: witness_version::InvalidWitnessVersionError) -> Self { Self::WitnessVersion(e) }
-}
-
/// Address type is either invalid or not supported in rust-bitcoin.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -118,26 +110,6 @@ impl std::error::Error for ParseError {
}
}
-#[cfg(feature = "alloc")]
-impl From<Base58Error> for ParseError {
- fn from(e: Base58Error) -> Self { Self::Base58(e) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<Bech32Error> for ParseError {
- fn from(e: Bech32Error) -> Self { Self::Bech32(e) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<UnknownHrpError> for ParseError {
- fn from(e: UnknownHrpError) -> Self { Self::Bech32(e.into()) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<NetworkValidationError> for ParseError {
- fn from(e: NetworkValidationError) -> Self { Self::NetworkValidation(e) }
-}
-
/// Unknown HRP error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -230,21 +202,6 @@ impl std::error::Error for Bech32Error {
}
}
-#[cfg(feature = "alloc")]
-impl From<witness_version::InvalidWitnessVersionError> for Bech32Error {
- fn from(e: witness_version::InvalidWitnessVersionError) -> Self { Self::WitnessVersion(e) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<witness_program::Error> for Bech32Error {
- fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<UnknownHrpError> for Bech32Error {
- fn from(e: UnknownHrpError) -> Self { Self::UnknownHrp(e) }
-}
-
/// Bech32 parsing related error.
// This wrapper exists because we do not want to expose the `bech32` crate in our public API.
#[cfg(feature = "alloc")]
@@ -305,18 +262,6 @@ impl std::error::Error for Base58Error {
}
}
-impl From<base58::DecodeCheckArrayError> for Base58Error {
- fn from(e: base58::DecodeCheckArrayError) -> Self { Self::ParseBase58(e) }
-}
-
-impl From<LegacyAddressTooLongError> for Base58Error {
- fn from(e: LegacyAddressTooLongError) -> Self { Self::LegacyAddressTooLong(e) }
-}
-
-impl From<InvalidLegacyPrefixError> for Base58Error {
- fn from(e: InvalidLegacyPrefixError) -> Self { Self::InvalidLegacyPrefix(e) }
-}
-
/// Legacy base58 address was too long, max 50 characters.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LegacyAddressTooLongError {
### addresses/src/lib.rs
@@ -1045,7 +1045,8 @@ impl Address<NetworkUnchecked> {
if self.is_valid_for_network(required) {
Ok(self.assume_checked())
} else {
- Err(NetworkValidationError { required, address: self }.into())
+ Err(NetworkValidationError { required, address: self })
+ .map_err(ParseError::NetworkValidation)
}
}
@@ -1072,11 +1073,12 @@ impl Address<NetworkUnchecked> {
pub fn from_bech32_str(s: &str) -> Result<Self, Bech32Error> {
let (hrp, witness_version, data) =
bech32::segwit::decode(s).map_err(|e| Bech32Error::ParseBech32(ParseBech32Error(e)))?;
- let version = WitnessVersion::try_from(witness_version.to_u8())?;
+ let version = WitnessVersion::try_from(witness_version.to_u8())
+ .map_err(Bech32Error::WitnessVersion)?;
let program = WitnessProgram::new(version, &data)
.expect("bech32 guarantees valid program length for witness");
- let hrp = KnownHrp::from_hrp(hrp)?;
+ let hrp = KnownHrp::from_hrp(hrp).map_err(Bech32Error::UnknownHrp)?;
let inner = AddressInner::Segwit { program, hrp };
Ok(Self::from_inner(inner))
}
@@ -1095,9 +1097,10 @@ impl Address<NetworkUnchecked> {
/// - [`SCRIPT_ADDRESS_PREFIX_TEST`]
pub fn from_base58_str(s: &str) -> Result<Self, Base58Error> {
if s.len() > 50 {
- return Err(LegacyAddressTooLongError { length: s.len() }.into());
+ return Err(LegacyAddressTooLongError { length: s.len() })
+ .map_err(Base58Error::LegacyAddressTooLong);
}
- let data = base58::decode_check_to_array::<21>(s)?;
+ let data = base58::decode_check_to_array::<21>(s).map_err(Base58Error::ParseBase58)?;
let (prefix, &data) = data.split_first();
@@ -1118,7 +1121,9 @@ impl Address<NetworkUnchecked> {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: NetworkKind::Test }
}
- invalid => return Err(InvalidLegacyPrefixError { invalid }.into()),
+ invalid =>
+ return Err(InvalidLegacyPrefixError { invalid })
+ .map_err(Base58Error::InvalidLegacyPrefix),
};
Ok(Self::from_inner(inner))
@@ -1169,18 +1174,20 @@ impl<U: NetworkValidationUnchecked> FromStr for Address<U> {
fn from_str(s: &str) -> Result<Self, ParseError> {
if ["bc1", "bcrt1", "tb1"].iter().any(|&prefix| s.to_lowercase().starts_with(prefix)) {
- let address = Address::from_bech32_str(s)?;
+ let address = Address::from_bech32_str(s).map_err(ParseError::Bech32)?;
// We know that `U` is only ever `NetworkUnchecked` but the compiler does not.
Ok(Self::from_inner(address.to_inner()))
} else if ["1", "2", "3", "m", "n"].iter().any(|&prefix| s.starts_with(prefix)) {
- let address = Address::from_base58_str(s)?;
+ let address = Address::from_base58_str(s).map_err(ParseError::Base58)?;
Ok(Self::from_inner(address.to_inner()))
} else {
let hrp = match s.rfind('1') {
Some(pos) => &s[..pos],
None => s,
};
- Err(UnknownHrpError(hrp.into()).into())
+ Err(UnknownHrpError(hrp.into()))
+ .map_err(Bech32Error::UnknownHrp)
+ .map_err(ParseError::Bech32)
}
}
}
### bitcoin/src/address.rs
@@ -86,8 +86,10 @@ crate::internal_macros::define_extension_trait! {
} else if script.is_witness_program() {
let opcode = script.first_opcode().expect("is_witness_program guarantees len > 4");
- let version = WitnessVersion::try_from(opcode)?;
- let program = WitnessProgram::new(version, &script.as_bytes()[2..])?;
+ let version = WitnessVersion::try_from(opcode)
+ .map_err(FromScriptError::WitnessVersion)?;
+ let program = WitnessProgram::new(version, &script.as_bytes()[2..])
+ .map_err(FromScriptError::WitnessProgram)?;
Ok(Self::from_witness_program(program, network))
} else {
Err(FromScriptError::UnrecognizedScript)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.