Decode an address string based on prefix

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`.
This commit is contained in:
Jamil Lambert, PhD 2024-10-17 20:01:28 +01:00
parent 02bf38e2a6
commit 3d994f7bdb
No known key found for this signature in database
GPG Key ID: 54DC29234AB5D2C0
1 changed files with 55 additions and 37 deletions

View File

@ -799,47 +799,21 @@ impl Address<NetworkUnchecked> {
}; };
Address(inner, PhantomData) Address(inner, PhantomData)
} }
}
impl From<Address> for ScriptBuf { /// Parse a bech32 Address string
fn from(a: Address) -> Self { a.script_pubkey() } pub fn from_bech32_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
} let (hrp, witness_version, data) = bech32::segwit::decode(s)?;
// 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) {
let version = WitnessVersion::try_from(witness_version.to_u8())?; let version = WitnessVersion::try_from(witness_version.to_u8())?;
let program = WitnessProgram::new(version, &data) let program = WitnessProgram::new(version, &data)
.expect("bech32 guarantees valid program length for witness"); .expect("bech32 guarantees valid program length for witness");
let hrp = KnownHrp::from_hrp(hrp)?; let hrp = KnownHrp::from_hrp(hrp)?;
let inner = AddressInner::Segwit { program, 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>, ParseError> {
if s.len() > 50 { if s.len() > 50 {
return Err(LegacyAddressTooLongError { length: s.len() }.into()); return Err(LegacyAddressTooLongError { length: s.len() }.into());
} }
@ -875,6 +849,50 @@ 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.
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)) {
Address::from_bech32_str(s)
} else if ["1", "2", "3", "m", "n"].iter().any(|&prefix| s.starts_with(prefix)) {
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 /// Convert a byte array of a pubkey hash into a segwit redeem hash
fn segwit_redeem_hash(pubkey_hash: PubkeyHash) -> hash160::Hash { fn segwit_redeem_hash(pubkey_hash: PubkeyHash) -> hash160::Hash {
let mut sha_engine = hash160::Hash::engine(); let mut sha_engine = hash160::Hash::engine();