base58: Add InvalidCharacterError for decoding

The `base58::decode` function can only return a single error type, add a
`InvalidCharacterError` struct (leaf error) to use as the return type.
This commit is contained in:
Tobin C. Harding 2024-02-21 16:41:47 +11:00
parent ec8609393b
commit 669d5e8fc6
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
4 changed files with 42 additions and 12 deletions

View File

@ -14,7 +14,7 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
std = ["hashes/std"]
std = ["hashes/std", "internals/std"]
[package.metadata.docs.rs]
all-features = true

View File

@ -4,12 +4,14 @@
use core::fmt;
use internals::write_err;
/// An error that might occur during base58 decoding.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// Invalid character encountered.
BadByte(u8),
/// Invalid character while decoding.
Decode(InvalidCharacterError),
/// Checksum was not correct (expected, actual).
BadChecksum(u32, u32),
/// The length (in bytes) of the object was not correct.
@ -32,7 +34,7 @@ impl fmt::Display for Error {
use Error::*;
match *self {
BadByte(b) => write!(f, "invalid base58 character {:#x}", b),
Decode(ref e) => write_err!(f, "decode"; e),
BadChecksum(exp, actual) =>
write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp),
InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell),
@ -51,8 +53,8 @@ impl std::error::Error for Error {
use Error::*;
match self {
BadByte(_)
| BadChecksum(_, _)
Decode(ref e) => Some(e),
BadChecksum(_, _)
| InvalidLength(_)
| InvalidExtendedKeyVersion(_)
| InvalidAddressVersion(_)
@ -60,3 +62,28 @@ impl std::error::Error for Error {
}
}
}
impl From<InvalidCharacterError> for Error {
#[inline]
fn from(e: InvalidCharacterError) -> Self { Self::Decode(e) }
}
/// Found a invalid ASCII byte while decoding base58 string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidCharacterError {
pub(super) invalid: u8,
}
impl InvalidCharacterError {
/// Returns the ASCII byte that is not a valid base58 character.
pub fn invalid_base58_character(&self) -> u8 { self.invalid }
}
impl fmt::Display for InvalidCharacterError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid base58 character {:#x}", self.invalid)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidCharacterError {}

View File

@ -36,7 +36,7 @@ use hashes::{sha256d, Hash};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use self::error::Error;
pub use self::error::{Error, InvalidCharacterError};
#[rustfmt::skip]
static BASE58_DIGITS: [Option<u8>; 128] = [
@ -59,19 +59,19 @@ static BASE58_DIGITS: [Option<u8>; 128] = [
];
/// Decodes a base58-encoded string into a byte vector.
pub fn decode(data: &str) -> Result<Vec<u8>, Error> {
pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
// 11/15 is just over log_256(58)
let mut scratch = vec![0u8; 1 + data.len() * 11 / 15];
// Build in base 256
for d58 in data.bytes() {
// Compute "X = X * 58 + next_digit" in base 256
if d58 as usize >= BASE58_DIGITS.len() {
return Err(Error::BadByte(d58));
return Err(InvalidCharacterError { invalid: d58 });
}
let mut carry = match BASE58_DIGITS[d58 as usize] {
Some(d58) => d58 as u32,
None => {
return Err(Error::BadByte(d58));
return Err(InvalidCharacterError { invalid: d58 });
}
};
for d256 in scratch.iter_mut().rev() {
@ -266,7 +266,10 @@ mod tests {
Some(hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d"))
);
// Non Base58 char.
assert_eq!(decode("¢").unwrap_err(), Error::BadByte(194));
assert_eq!(
decode("¢").unwrap_err(),
InvalidCharacterError { invalid: 194 }
);
}
#[test]

View File

@ -2500,7 +2500,7 @@ mod tests {
use super::ParseAmountError as E;
assert_eq!(Amount::from_str("x BTC"), Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 }).into()));
assert_eq!(Amount::from_str("x BTC"), Err(InvalidCharacterError { invalid_char: 'x', position: 0 }.into()));
assert_eq!(
Amount::from_str("xBTC"),
Err(Unknown(UnknownDenominationError("xBTC".into())).into()),