bs58/
alphabet.rs

1//! Support for configurable alphabets
2
3use core::fmt;
4
5/// Prepared Alphabet for
6/// [`EncodeBuilder::with_alphabet`](crate::encode::EncodeBuilder::with_alphabet) and
7/// [`DecodeBuilder::with_alphabet`](crate::decode::DecodeBuilder::with_alphabet).
8#[derive(Clone, Copy)]
9pub struct Alphabet {
10    pub(crate) encode: [u8; 58],
11    pub(crate) decode: [u8; 128],
12}
13
14/// Errors that could occur when preparing a Base58 alphabet.
15#[non_exhaustive]
16#[derive(Copy, Clone, Debug, Eq, PartialEq)]
17pub enum Error {
18    /// The alphabet contained a duplicate character at at least 2 indexes.
19    DuplicateCharacter {
20        /// The duplicate character encountered.
21        character: char,
22        /// The first index the character was seen at.
23        first: usize,
24        /// The second index the character was seen at.
25        second: usize,
26    },
27
28    /// The alphabet contained a multi-byte (or non-utf8) character.
29    NonAsciiCharacter {
30        /// The index at which the non-ASCII character was seen.
31        index: usize,
32    },
33}
34
35impl Alphabet {
36    /// Bitcoin's alphabet as defined in their Base58Check encoding.
37    ///
38    /// See <https://en.bitcoin.it/wiki/Base58Check_encoding#Base58_symbol_chart>
39    pub const BITCOIN: &'static Self =
40        &Self::new_unwrap(b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
41
42    /// Monero's alphabet as defined in this forum post.
43    ///
44    /// See <https://forum.getmonero.org/4/academic-and-technical/221/creating-a-standard-for-physical-coins>
45    pub const MONERO: &'static Self =
46        &Self::new_unwrap(b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
47
48    /// Ripple's alphabet as defined in their wiki.
49    ///
50    /// See <https://wiki.ripple.com/Encodings>
51    pub const RIPPLE: &'static Self =
52        &Self::new_unwrap(b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");
53
54    /// Flickr's alphabet for creating short urls from photo ids.
55    ///
56    /// See <https://www.flickr.com/groups/api/discuss/72157616713786392/>
57    pub const FLICKR: &'static Self =
58        &Self::new_unwrap(b"123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ");
59
60    /// The default alphabet used if none is given. Currently is the
61    /// [`BITCOIN`](Self::BITCOIN) alphabet.
62    pub const DEFAULT: &'static Self = Self::BITCOIN;
63
64    /// Create prepared alphabet, checks that the alphabet is pure ASCII and that there are no
65    /// duplicate characters, which would result in inconsistent encoding/decoding
66    ///
67    /// ```rust
68    /// let alpha = bs58::Alphabet::new(
69    ///     b" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXY"
70    /// )?;
71    ///
72    /// let decoded = bs58::decode("he11owor1d")
73    ///     .with_alphabet(bs58::Alphabet::RIPPLE)
74    ///     .into_vec()?;
75    /// let encoded = bs58::encode(decoded)
76    ///     .with_alphabet(&alpha)
77    ///     .into_string();
78    ///
79    /// assert_eq!("#ERRN)N RD", encoded);
80    /// # Ok::<(), Box<dyn std::error::Error>>(())
81    /// ```
82    /// ## Errors
83    ///
84    /// ### Duplicate Character
85    ///
86    /// ```rust
87    /// let alpha = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
88    /// assert_eq!(
89    ///     bs58::alphabet::Error::DuplicateCharacter { character: 'a', first: 0, second: 1 },
90    ///     bs58::Alphabet::new(alpha).unwrap_err());
91    /// ```
92    ///
93    /// ### Non-ASCII Character
94    ///
95    /// ```rust
96    /// let mut alpha = *b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
97    /// alpha[1] = 255;
98    /// assert_eq!(
99    ///     bs58::alphabet::Error::NonAsciiCharacter { index: 1 },
100    ///     bs58::Alphabet::new(&alpha).unwrap_err());
101    /// ```
102    pub const fn new(base: &[u8; 58]) -> Result<Self, Error> {
103        let mut encode = [0x00; 58];
104        let mut decode = [0xFF; 128];
105
106        let mut i = 0;
107        while i < encode.len() {
108            if base[i] >= 128 {
109                return Err(Error::NonAsciiCharacter { index: i });
110            }
111            if decode[base[i] as usize] != 0xFF {
112                return Err(Error::DuplicateCharacter {
113                    character: base[i] as char,
114                    first: decode[base[i] as usize] as usize,
115                    second: i,
116                });
117            }
118            encode[i] = base[i];
119            decode[base[i] as usize] = i as u8;
120            i += 1;
121        }
122
123        Ok(Self { encode, decode })
124    }
125
126    /// Same as [`Self::new`], but gives a panic instead of an [`Err`] on bad input.
127    ///
128    /// Intended to support usage in `const` context until [`Result::unwrap`] is able to be called.
129    ///
130    /// ```rust
131    /// const ALPHA: &'static bs58::Alphabet = &bs58::Alphabet::new_unwrap(
132    ///     b" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXY"
133    /// );
134    ///
135    /// let decoded = bs58::decode("he11owor1d")
136    ///     .with_alphabet(bs58::Alphabet::RIPPLE)
137    ///     .into_vec()?;
138    /// let encoded = bs58::encode(decoded)
139    ///     .with_alphabet(ALPHA)
140    ///     .into_string();
141    ///
142    /// assert_eq!("#ERRN)N RD", encoded);
143    /// # Ok::<(), Box<dyn std::error::Error>>(())
144    /// ```
145    ///
146    /// If your alphabet is inconsistent then this will fail to compile in a `const` context:
147    ///
148    /// ```compile_fail
149    /// const _: &'static bs58::Alphabet = &bs58::Alphabet::new_unwrap(
150    ///     b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
151    /// );
152    /// ```
153    pub const fn new_unwrap(base: &[u8; 58]) -> Self {
154        let result = Self::new(base);
155        #[allow(unconditional_panic)] // https://github.com/rust-lang/rust/issues/78803
156        [][match result {
157            Ok(alphabet) => return alphabet,
158            Err(_) => 0,
159        }]
160    }
161}
162
163impl fmt::Debug for Alphabet {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        if let Ok(s) = core::str::from_utf8(&self.encode) {
166            f.debug_tuple("Alphabet").field(&s).finish()
167        } else {
168            unreachable!()
169        }
170    }
171}
172
173#[cfg(feature = "std")]
174impl std::error::Error for Error {}
175
176impl fmt::Display for Error {
177    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178        match *self {
179            Error::DuplicateCharacter {
180                character,
181                first,
182                second,
183            } => write!(
184                f,
185                "alphabet contained a duplicate character `{}` at indexes {} and {}",
186                character, first, second,
187            ),
188            Error::NonAsciiCharacter { index } => {
189                write!(f, "alphabet contained a non-ascii character at {}", index)
190            }
191        }
192    }
193}
194
195// Force evaluation of the associated constants to make sure they don't error
196const _: () = {
197    let _ = Alphabet::BITCOIN;
198    let _ = Alphabet::MONERO;
199    let _ = Alphabet::RIPPLE;
200    let _ = Alphabet::FLICKR;
201    let _ = Alphabet::DEFAULT;
202};
203
204#[test]
205#[should_panic]
206fn test_new_unwrap_does_panic() {
207    Alphabet::new_unwrap(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
208}