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}