Skip to main content

bluetape_rs_codec/
base58.rs

1//! Strict Base58 encoding and decoding.
2
3use std::error::Error;
4use std::fmt;
5
6use crate::base_n::{BaseNDecodeError, decode_base_n, encode_base_n};
7
8const BITCOIN_BASE58_ALPHABET: &[u8] =
9    b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
10
11/// Error returned when Base58 decoding rejects caller-owned input.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum Base58DecodeError {
15    /// The encoded text contains a byte outside the Bitcoin Base58 alphabet.
16    InvalidCharacter {
17        /// Zero-based byte position of the invalid byte.
18        index: usize,
19        /// Invalid byte value.
20        byte: u8,
21    },
22}
23
24impl fmt::Display for Base58DecodeError {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::InvalidCharacter { index, byte } => {
28                write!(
29                    f,
30                    "base58 input contains invalid byte 0x{byte:02x} at position {index}"
31                )
32            }
33        }
34    }
35}
36
37impl Error for Base58DecodeError {}
38
39impl From<BaseNDecodeError> for Base58DecodeError {
40    fn from(error: BaseNDecodeError) -> Self {
41        match error {
42            BaseNDecodeError::InvalidCharacter { index, byte } => {
43                Self::InvalidCharacter { index, byte }
44            }
45        }
46    }
47}
48
49/// Encodes bytes with the Bitcoin Base58 alphabet.
50///
51/// Leading zero bytes are preserved as leading `1` characters.
52///
53/// # Examples
54///
55/// ```
56/// use bluetape_rs_codec::encode_base58;
57///
58/// assert_eq!(encode_base58(b"Hello, World!"), "72k1xXWG59fYdzSNoA");
59/// ```
60#[must_use]
61pub fn encode_base58(bytes: impl AsRef<[u8]>) -> String {
62    encode_base_n(bytes.as_ref(), BITCOIN_BASE58_ALPHABET)
63}
64
65/// Decodes Bitcoin Base58 text into bytes.
66///
67/// # Examples
68///
69/// ```
70/// use bluetape_rs_codec::decode_base58;
71///
72/// assert_eq!(decode_base58("72k1xXWG59fYdzSNoA")?, b"Hello, World!");
73/// # Ok::<(), bluetape_rs_codec::Base58DecodeError>(())
74/// ```
75///
76/// # Errors
77///
78/// Returns [`Base58DecodeError`] when input contains bytes outside the Bitcoin
79/// Base58 alphabet.
80pub fn decode_base58(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base58DecodeError> {
81    decode_base_n(encoded.as_ref(), BITCOIN_BASE58_ALPHABET).map_err(Into::into)
82}