Merge rust-bitcoin/rust-bitcoin#3481: Decode an address string based on prefix

9c2ac46902 Split up ParseError (Jamil Lambert, PhD)
3d994f7bdb Decode an address string based on prefix (Jamil Lambert, PhD)

Pull request description:

  When a decoding error occurs for a bech32 address string the error is discarded and the same address string is attempted to be decoded as base58.  This then incorrectly returns a base58 error.

  Check the string prefix and decode as bech32 or base58 and return the relevant error.  If the prefix is unknown return an `UnknownHrpError`.

  Close #3044

ACKs for top commit:
  tcharding:
    ACK 9c2ac46902
  apoelstra:
    ACK 9c2ac46902ae2e6f2513ee125ea5c89953ac89a2; successfully ran local tests

Tree-SHA512: 40c94328828af86723e84d4196e8949430fb9a15efd8865c18cb5048fe59b8a2514d97f4809d828353b78c010544a8a6d4589a8c9c7fbd75d9d0ecceb3151e8f
This commit is contained in:
merge-script 2024-10-23 16:59:57 +00:00
commit 2f40c30f73
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 196 additions and 92 deletions

View File

@ -76,22 +76,10 @@ impl std::error::Error for UnknownAddressTypeError {
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
/// Base58 error.
Base58(base58::Error),
/// Base58 legacy decoding error.
Base58(Base58Error),
/// Bech32 segwit decoding error.
Bech32(bech32::segwit::DecodeError),
/// A witness version conversion/parsing error.
WitnessVersion(witness_version::TryFromError),
/// A witness program error.
WitnessProgram(witness_program::Error),
/// Tried to parse an unknown HRP.
UnknownHrp(UnknownHrpError),
/// Legacy address is too long.
LegacyAddressTooLong(LegacyAddressTooLongError),
/// Invalid base58 payload data length for legacy address.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Invalid legacy address prefix in base58 data payload.
InvalidLegacyPrefix(InvalidLegacyPrefixError),
Bech32(Bech32Error),
/// Address's network differs from required one.
NetworkValidation(NetworkValidationError),
}
@ -104,13 +92,7 @@ impl fmt::Display for ParseError {
match *self {
Base58(ref e) => write_err!(f, "base58 error"; e),
Bech32(ref e) => write_err!(f, "bech32 segwit decoding error"; e),
WitnessVersion(ref e) => write_err!(f, "witness version conversion/parsing error"; e),
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
UnknownHrp(ref e) => write_err!(f, "tried to parse an unknown hrp"; e),
LegacyAddressTooLong(ref e) => write_err!(f, "legacy address base58 string"; e),
InvalidBase58PayloadLength(ref e) => write_err!(f, "legacy address base58 data"; e),
InvalidLegacyPrefix(ref e) => write_err!(f, "legacy address base58 prefix"; e),
Bech32(ref e) => write_err!(f, "bech32 error"; e),
NetworkValidation(ref e) => write_err!(f, "validation error"; e),
}
}
@ -124,47 +106,21 @@ impl std::error::Error for ParseError {
match *self {
Base58(ref e) => Some(e),
Bech32(ref e) => Some(e),
WitnessVersion(ref e) => Some(e),
WitnessProgram(ref e) => Some(e),
UnknownHrp(ref e) => Some(e),
LegacyAddressTooLong(ref e) => Some(e),
InvalidBase58PayloadLength(ref e) => Some(e),
InvalidLegacyPrefix(ref e) => Some(e),
NetworkValidation(ref e) => Some(e),
}
}
}
impl From<base58::Error> for ParseError {
fn from(e: base58::Error) -> Self { Self::Base58(e) }
impl From<Base58Error> for ParseError {
fn from(e: Base58Error) -> Self { Self::Base58(e) }
}
impl From<bech32::segwit::DecodeError> for ParseError {
fn from(e: bech32::segwit::DecodeError) -> Self { Self::Bech32(e) }
}
impl From<witness_version::TryFromError> for ParseError {
fn from(e: witness_version::TryFromError) -> Self { Self::WitnessVersion(e) }
}
impl From<witness_program::Error> for ParseError {
fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
impl From<Bech32Error> for ParseError {
fn from(e: Bech32Error) -> Self { Self::Bech32(e) }
}
impl From<UnknownHrpError> for ParseError {
fn from(e: UnknownHrpError) -> Self { Self::UnknownHrp(e) }
}
impl From<LegacyAddressTooLongError> for ParseError {
fn from(e: LegacyAddressTooLongError) -> Self { Self::LegacyAddressTooLong(e) }
}
impl From<InvalidBase58PayloadLengthError> for ParseError {
fn from(e: InvalidBase58PayloadLengthError) -> Self { Self::InvalidBase58PayloadLength(e) }
}
impl From<InvalidLegacyPrefixError> for ParseError {
fn from(e: InvalidLegacyPrefixError) -> Self { Self::InvalidLegacyPrefix(e) }
fn from(e: UnknownHrpError) -> ParseError { Self::Bech32(e.into()) }
}
impl From<NetworkValidationError> for ParseError {
@ -205,6 +161,124 @@ impl fmt::Display for NetworkValidationError {
#[cfg(feature = "std")]
impl std::error::Error for NetworkValidationError {}
/// Bech32 related error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Bech32Error {
/// Parse segwit Bech32 error.
ParseBech32(bech32::segwit::DecodeError),
/// A witness version conversion/parsing error.
WitnessVersion(witness_version::TryFromError),
/// A witness program error.
WitnessProgram(witness_program::Error),
/// Tried to parse an unknown HRP.
UnknownHrp(UnknownHrpError),
}
internals::impl_from_infallible!(Bech32Error);
impl fmt::Display for Bech32Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Bech32Error::*;
match *self {
ParseBech32(ref e) => write_err!(f, "segwit parsing error"; e),
WitnessVersion(ref e) => write_err!(f, "witness version conversion/parsing error"; e),
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
UnknownHrp(ref e) => write_err!(f, "unknown hrp error"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Bech32Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Bech32Error::*;
match *self {
ParseBech32(ref e) => Some(e),
WitnessVersion(ref e) => Some(e),
WitnessProgram(ref e) => Some(e),
UnknownHrp(ref e) => Some(e),
}
}
}
impl From<bech32::segwit::DecodeError> for Bech32Error {
fn from(e: bech32::segwit::DecodeError) -> Self { Self::ParseBech32(e) }
}
impl From<witness_version::TryFromError> for Bech32Error {
fn from(e: witness_version::TryFromError) -> Self { Self::WitnessVersion(e) }
}
impl From<witness_program::Error> for Bech32Error {
fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
}
impl From<UnknownHrpError> for Bech32Error {
fn from(e: UnknownHrpError) -> Self { Self::UnknownHrp(e) }
}
/// Base58 related error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Base58Error {
/// Parse legacy Base58 error.
ParseBase58(base58::Error),
/// Legacy address is too long.
LegacyAddressTooLong(LegacyAddressTooLongError),
/// Invalid base58 payload data length for legacy address.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Invalid legacy address prefix in base58 data payload.
InvalidLegacyPrefix(InvalidLegacyPrefixError),
}
internals::impl_from_infallible!(Base58Error);
impl fmt::Display for Base58Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Base58Error::*;
match *self {
ParseBase58(ref e) => write_err!(f, "legacy parsing error"; e),
LegacyAddressTooLong(ref e) => write_err!(f, "legacy address length error"; e),
InvalidBase58PayloadLength(ref e) => write_err!(f, "legacy payload length error"; e),
InvalidLegacyPrefix(ref e) => write_err!(f, "legacy prefix error"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Base58Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Base58Error::*;
match *self {
ParseBase58(ref e) => Some(e),
LegacyAddressTooLong(ref e) => Some(e),
InvalidBase58PayloadLength(ref e) => Some(e),
InvalidLegacyPrefix(ref e) => Some(e),
}
}
}
impl From<base58::Error> for Base58Error {
fn from(e: base58::Error) -> Self { Self::ParseBase58(e) }
}
impl From<LegacyAddressTooLongError> for Base58Error {
fn from(e: LegacyAddressTooLongError) -> Self { Self::LegacyAddressTooLong(e) }
}
impl From<InvalidBase58PayloadLengthError> for Base58Error {
fn from(e: InvalidBase58PayloadLengthError) -> Self { Self::InvalidBase58PayloadLength(e) }
}
impl From<InvalidLegacyPrefixError> for Base58Error {
fn from(e: InvalidLegacyPrefixError) -> Self { Self::InvalidLegacyPrefix(e) }
}
/// Decoded base58 data was an invalid length.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidBase58PayloadLengthError {

View File

@ -59,8 +59,9 @@ use crate::taproot::TapNodeHash;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use self::error::{
FromScriptError, InvalidBase58PayloadLengthError, InvalidLegacyPrefixError, LegacyAddressTooLongError,
NetworkValidationError, ParseError, UnknownAddressTypeError, UnknownHrpError,
Base58Error, Bech32Error, FromScriptError, InvalidBase58PayloadLengthError,
InvalidLegacyPrefixError, LegacyAddressTooLongError, NetworkValidationError,
ParseError, UnknownAddressTypeError, UnknownHrpError
};
/// The different types of addresses.
@ -799,47 +800,21 @@ impl Address<NetworkUnchecked> {
};
Address(inner, PhantomData)
}
}
impl From<Address> for ScriptBuf {
fn from(a: Address) -> Self { a.script_pubkey() }
}
// Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should
// be used in QR codes, see [`Address::to_qr_uri`].
impl fmt::Display for Address {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) }
}
impl<V: NetworkValidation> fmt::Debug for Address<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if V::IS_CHECKED {
fmt::Display::fmt(&self.0, f)
} else {
write!(f, "Address<NetworkUnchecked>(")?;
fmt::Display::fmt(&self.0, f)?;
write!(f, ")")
}
}
}
/// Address can be parsed only with `NetworkUnchecked`.
impl FromStr for Address<NetworkUnchecked> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) {
/// Parse a bech32 Address string
pub fn from_bech32_str(s: &str) -> Result<Address<NetworkUnchecked>, Bech32Error> {
let (hrp, witness_version, data) = bech32::segwit::decode(s)?;
let version = WitnessVersion::try_from(witness_version.to_u8())?;
let program = WitnessProgram::new(version, &data)
.expect("bech32 guarantees valid program length for witness");
let hrp = KnownHrp::from_hrp(hrp)?;
let inner = AddressInner::Segwit { program, hrp };
return Ok(Address(inner, PhantomData));
Ok(Address(inner, PhantomData))
}
// If segwit decoding fails, assume its a legacy address.
/// Parse a base58 Address string
pub fn from_base58_str(s: &str) -> Result<Address<NetworkUnchecked>, Base58Error> {
if s.len() > 50 {
return Err(LegacyAddressTooLongError { length: s.len() }.into());
}
@ -875,6 +850,61 @@ impl FromStr for Address<NetworkUnchecked> {
}
}
impl From<Address> for ScriptBuf {
fn from(a: Address) -> Self { a.script_pubkey() }
}
// Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should
// be used in QR codes, see [`Address::to_qr_uri`].
impl fmt::Display for Address {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) }
}
impl<V: NetworkValidation> fmt::Debug for Address<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if V::IS_CHECKED {
fmt::Display::fmt(&self.0, f)
} else {
write!(f, "Address<NetworkUnchecked>(")?;
fmt::Display::fmt(&self.0, f)?;
write!(f, ")")
}
}
}
/// Address can be parsed only with `NetworkUnchecked`.
///
/// Only segwit bech32 addresses prefixed with `bc`, `bcrt` or `tb` and legacy base58 addresses
/// prefixed with `1`, `2, `3`, `m` or `n` are supported.
///
/// # Errors
///
/// - [`ParseError::Bech32`] if the segwit address begins with a `bc`, `bcrt` or `tb` and is not a
/// valid bech32 address.
///
/// - [`ParseError::Base58`] if the legacy address begins with a `1`, `2`, `3`, `m` or `n` and is
/// not a valid base58 address.
///
/// - [`UnknownHrpError`] if the address does not begin with one of the above segwit or
/// legacy prifixes.
impl FromStr for Address<NetworkUnchecked> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
if ["bc1", "bcrt1", "tb1"].iter().any(|&prefix| s.to_lowercase().starts_with(prefix)) {
Ok(Address::from_bech32_str(s)?)
} else if ["1", "2", "3", "m", "n"].iter().any(|&prefix| s.starts_with(prefix)) {
Ok(Address::from_base58_str(s)?)
} else {
let hrp = match s.rfind('1') {
Some(pos) => &s[..pos],
None => s,
};
Err(UnknownHrpError(hrp.to_owned()).into())
}
}
}
/// Convert a byte array of a pubkey hash into a segwit redeem hash
fn segwit_redeem_hash(pubkey_hash: PubkeyHash) -> hash160::Hash {
let mut sha_engine = hash160::Hash::engine();