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] [features]
default = ["std"] default = ["std"]
std = ["hashes/std"] std = ["hashes/std", "internals/std"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -4,12 +4,14 @@
use core::fmt; use core::fmt;
use internals::write_err;
/// An error that might occur during base58 decoding. /// An error that might occur during base58 decoding.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Invalid character encountered. /// Invalid character while decoding.
BadByte(u8), Decode(InvalidCharacterError),
/// Checksum was not correct (expected, actual). /// Checksum was not correct (expected, actual).
BadChecksum(u32, u32), BadChecksum(u32, u32),
/// The length (in bytes) of the object was not correct. /// The length (in bytes) of the object was not correct.
@ -32,7 +34,7 @@ impl fmt::Display for Error {
use Error::*; use Error::*;
match *self { match *self {
BadByte(b) => write!(f, "invalid base58 character {:#x}", b), Decode(ref e) => write_err!(f, "decode"; e),
BadChecksum(exp, actual) => BadChecksum(exp, actual) =>
write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp), write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp),
InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell), InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell),
@ -51,8 +53,8 @@ impl std::error::Error for Error {
use Error::*; use Error::*;
match self { match self {
BadByte(_) Decode(ref e) => Some(e),
| BadChecksum(_, _) BadChecksum(_, _)
| InvalidLength(_) | InvalidLength(_)
| InvalidExtendedKeyVersion(_) | InvalidExtendedKeyVersion(_)
| InvalidAddressVersion(_) | 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. #[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)] #[doc(inline)]
pub use self::error::Error; pub use self::error::{Error, InvalidCharacterError};
#[rustfmt::skip] #[rustfmt::skip]
static BASE58_DIGITS: [Option<u8>; 128] = [ 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. /// 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) // 11/15 is just over log_256(58)
let mut scratch = vec![0u8; 1 + data.len() * 11 / 15]; let mut scratch = vec![0u8; 1 + data.len() * 11 / 15];
// Build in base 256 // Build in base 256
for d58 in data.bytes() { for d58 in data.bytes() {
// Compute "X = X * 58 + next_digit" in base 256 // Compute "X = X * 58 + next_digit" in base 256
if d58 as usize >= BASE58_DIGITS.len() { 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] { let mut carry = match BASE58_DIGITS[d58 as usize] {
Some(d58) => d58 as u32, Some(d58) => d58 as u32,
None => { None => {
return Err(Error::BadByte(d58)); return Err(InvalidCharacterError { invalid: d58 });
} }
}; };
for d256 in scratch.iter_mut().rev() { for d256 in scratch.iter_mut().rev() {
@ -266,7 +266,10 @@ mod tests {
Some(hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d")) Some(hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d"))
); );
// Non Base58 char. // Non Base58 char.
assert_eq!(decode("¢").unwrap_err(), Error::BadByte(194)); assert_eq!(
decode("¢").unwrap_err(),
InvalidCharacterError { invalid: 194 }
);
} }
#[test] #[test]

View File

@ -2500,7 +2500,7 @@ mod tests {
use super::ParseAmountError as E; 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!( assert_eq!(
Amount::from_str("xBTC"), Amount::from_str("xBTC"),
Err(Unknown(UnknownDenominationError("xBTC".into())).into()), Err(Unknown(UnknownDenominationError("xBTC".into())).into()),