spideroak_base58/
base58.rs

1use core::{
2    borrow::Borrow,
3    cmp::{Ord, Ordering, PartialEq, PartialOrd},
4    ffi::CStr,
5    fmt,
6    hash::{Hash, Hasher},
7    ops::Deref,
8    result::Result,
9    str::{self, FromStr},
10};
11
12use buggy::{Bug, BugExt};
13
14use crate::arith::{div_ww, mul_add_ww};
15
16const ALPHABET: [u8; 58] = [
17    b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G',
18    b'H', b'J', b'K', b'L', b'M', b'N', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y',
19    b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p',
20    b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
21];
22
23/// 58^i
24const RADII: [u64; 11] = [
25    0,
26    58,
27    58 * 58,
28    58 * 58 * 58,
29    58 * 58 * 58 * 58,
30    58 * 58 * 58 * 58 * 58,
31    58 * 58 * 58 * 58 * 58 * 58,
32    58 * 58 * 58 * 58 * 58 * 58 * 58,
33    58 * 58 * 58 * 58 * 58 * 58 * 58 * 58,
34    58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58,
35    58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58,
36];
37
38const B58: [u8; 256] = [
39    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
40    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
41    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 255, 255,
42    255, 255, 255, 255, 255, 9, 10, 11, 12, 13, 14, 15, 16, 255, 17, 18, 19, 20, 21, 255, 22, 23,
43    24, 25, 26, 27, 28, 29, 30, 31, 32, 255, 255, 255, 255, 255, 255, 33, 34, 35, 36, 37, 38, 39,
44    40, 41, 42, 43, 255, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255,
45    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
46    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
47    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
48    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
49    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
50    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
51    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
52];
53
54/// Implemented by types that can encode themselves as Base58.
55pub trait ToBase58 {
56    /// A Base58 string.
57    type Output: Borrow<str>;
58
59    /// Encodes itself as a Base58 string.
60    fn to_base58(&self) -> Self::Output;
61}
62
63/// The Base58 could not be decoded.
64#[derive(Clone, Debug)]
65pub enum DecodeError {
66    /// The input is not valid base58.
67    BadInput,
68    /// An internal bug occurred.
69    Bug(Bug),
70}
71
72impl From<Bug> for DecodeError {
73    fn from(bug: Bug) -> Self {
74        DecodeError::Bug(bug)
75    }
76}
77
78impl fmt::Display for DecodeError {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Self::BadInput => write!(f, "bad input"),
82            Self::Bug(bug) => write!(f, "{bug}"),
83        }
84    }
85}
86
87impl core::error::Error for DecodeError {}
88
89// Generate PartialEq for $lhs and $rhs.
90macro_rules! impl_eq {
91    ($lhs:ty, $rhs:ty) => {
92        #[allow(unused_lifetimes)]
93        impl<'a, 'b> PartialEq<$rhs> for $lhs {
94            #[inline]
95            fn eq(&self, other: &$rhs) -> bool {
96                PartialEq::eq(&self[..], &other[..])
97            }
98        }
99
100        #[allow(unused_lifetimes)]
101        impl<'a, 'b> PartialEq<$lhs> for $rhs {
102            #[inline]
103            fn eq(&self, other: &$lhs) -> bool {
104                PartialEq::eq(&self[..], &other[..])
105            }
106        }
107    };
108}
109
110// Generate fixed-size encodings.
111macro_rules! encode_x {
112    ($($name:ident => $size:expr),+ $(,)?) => {
113        $(
114            #[doc = concat!("A Base58-encoded ", stringify!($size), "-byte value.")]
115            #[derive(Copy, Clone)]
116            pub struct $name {
117                data: [u8; Self::BUFFER_SIZE],
118            }
119
120            impl $name {
121                /// The size in bytes of the encoded value.
122                pub const B58_SIZE: usize = ($size * 1375) / 1000;
123
124                /// The size in bytes of the encoded value, with a null terminator.
125                pub const BUFFER_SIZE: usize = Self::B58_SIZE + 1;
126            }
127
128            impl fmt::Display for $name {
129                #[inline]
130                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131                    fmt::Display::fmt(self.as_str(), f)
132                }
133            }
134
135            impl fmt::Debug for $name {
136                #[inline]
137                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138                    fmt::Debug::fmt(self.as_str(), f)
139                }
140            }
141
142            impl AsRef<[u8]> for $name {
143                #[inline]
144                fn as_ref(&self) -> &[u8] {
145                    self.as_bytes()
146                }
147            }
148
149            impl AsRef<str> for $name {
150                #[inline]
151                fn as_ref(&self) -> &str {
152                    self.as_str()
153                }
154            }
155
156            impl AsRef<CStr> for $name {
157                #[inline]
158                fn as_ref(&self) -> &CStr {
159                    self.as_cstr()
160                }
161            }
162
163            impl Borrow<str> for $name {
164                #[inline]
165                fn borrow(&self) -> &str {
166                    self.as_str()
167                }
168            }
169
170            impl Deref for $name {
171                type Target = str;
172
173                #[inline]
174                fn deref(&self) -> &str {
175                    self.as_str()
176                }
177            }
178
179            impl TryFrom<&str> for $name {
180                type Error = DecodeError;
181
182                #[inline]
183                fn try_from(s: &str) -> Result<Self, DecodeError> {
184                    Self::from_str(s)
185                }
186            }
187
188            impl TryFrom<$name> for [u8; $size] {
189                type Error = DecodeError;
190
191                #[inline]
192                fn try_from(s: $name) -> Result<Self, DecodeError> {
193                    $name::decode(&s)
194                }
195            }
196
197            impl FromStr for $name {
198                type Err = DecodeError;
199
200                fn from_str(s: &str) -> Result<Self, DecodeError> {
201                    let b = s.as_bytes();
202                    Self::decode(b)?;
203                    // It's valid Base58, so just copy it over.
204                    let mut v = Self::default();
205                    let start = Self::B58_SIZE.checked_sub(b.len()).assume("decode ensures it will fit")?;
206                    v.data[start..Self::B58_SIZE].copy_from_slice(b);
207                    Ok(v)
208                }
209            }
210
211            impl Default for $name {
212                #[inline]
213                fn default() -> Self  {
214                    let mut this = Self {
215                        data: [b'1'; Self::BUFFER_SIZE],
216                    };
217                    this.data[Self::BUFFER_SIZE - 1] = 0;
218                    this
219                }
220            }
221
222            impl Eq for $name {}
223            impl PartialEq for $name {
224                #[inline]
225                fn eq(&self, other: &Self) -> bool {
226                    PartialEq::eq(&self[..], &other[..])
227                }
228            }
229            impl_eq!($name, str);
230            impl_eq!($name, &'a str);
231            #[cfg(feature = "alloc")]
232            #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
233            impl_eq!(alloc::borrow::Cow<'a, str>, $name);
234            #[cfg(feature = "alloc")]
235            #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
236            impl_eq!($name, alloc::string::String);
237
238            impl Ord for $name {
239                #[inline]
240                fn cmp(&self, other: &Self) -> Ordering {
241                    Ord::cmp(&self[..], &other[..])
242                }
243            }
244
245            impl PartialOrd for $name {
246                #[inline]
247                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
248                    Some(self.cmp(other))
249                }
250            }
251
252            impl Hash for $name {
253                #[inline]
254                fn hash<H: Hasher>(&self, hasher: &mut H) {
255                    (**self).hash(hasher)
256                }
257            }
258
259            impl ToBase58 for [u8; $size] {
260                type Output = $name;
261
262                fn to_base58(&self) -> Self::Output {
263                    $name::encode(self)
264                }
265            }
266
267            impl $name {
268                /// Returns a string slice of the string's contents.
269                #[inline]
270                pub fn as_str(&self) -> &str {
271                    str::from_utf8(self.as_bytes())
272                        .expect("should be valid UTF-8")
273                }
274
275                /// Returns a byte slice of the string's contents.
276                #[inline]
277                pub fn as_bytes(&self) -> &[u8; Self::B58_SIZE] {
278                    self.data[..Self::B58_SIZE].try_into().expect("array size matches slice")
279                }
280
281                /// Returns a null terminated cstr of the string's contents.
282                #[inline]
283                pub fn as_cstr(&self) -> &CStr {
284                    CStr::from_bytes_with_nul(&self.data).expect("should be valid C string")
285                }
286
287                /// Decodes `s` as bytes.
288                pub fn decode<T: AsRef<[u8]>>(s: T) -> Result<[u8; $size], DecodeError> {
289                    let mut x = Uint::<{ $size/8 }, $size>::new();
290                    // Work 10 bytes at a time (see `RADIX`, etc).
291                    for chunk in s.as_ref().chunks(10) {
292                        let total = chunk
293                            .iter()
294                            .map(|c| B58[*c as usize])
295                            .try_fold(0, |acc: u64, v| {
296                                if v == 255 {
297                                    Err(DecodeError::BadInput)
298                                } else {
299                                    Ok(acc.checked_mul(58).assume("doesn't wrap")?.checked_add(u64::from(v)).assume("doesn't wrap")?)
300                                }
301                            })?;
302                        if !x.fma(RADII[chunk.len()], total) {
303                            return Err(DecodeError::BadInput);
304                        }
305                    }
306                    Ok(x.to_be_bytes())
307                }
308
309                /// Encodes `b` as a Base58 string.
310                pub fn encode(b: &[u8; $size]) -> Self {
311                    let mut dst = Self::default();
312
313                    let mut x = Uint::<{ $size/8 }, $size>::from_be_bytes(b);
314
315                    let mut i = Self::B58_SIZE;
316                    while !x.is_zero() {
317                        let mut r = x.quo_radix();
318                        if x.is_zero() {
319                            while r > 0 {
320                                i = i.checked_sub(1).expect("i must be non-zero");
321                                dst.data[i] = ALPHABET[(r % 58) as usize];
322                                r /= 58;
323                            }
324                        } else {
325                            for _ in 0..10 {
326                                i = i.checked_sub(1).expect("i must be non-zero");
327                                dst.data[i] = ALPHABET[(r % 58) as usize];
328                                r /= 58;
329                            }
330                        }
331                    }
332
333                    dst
334                }
335            }
336        )+
337    };
338}
339encode_x! {
340    String16 => 16,
341    String32 => 32,
342    String64 => 64,
343}
344
345/// An unsigned N-bit integer.
346///
347/// - `W` is the number of words
348/// - `B` is the number of bytes (`B = W*8`)
349///
350/// This should just be `N`, but `const_generic_exprs` is still
351/// +nightly.
352#[derive(Debug, Eq, PartialEq)]
353struct Uint<const W: usize, const B: usize> {
354    words: [u64; W],
355}
356
357impl<const W: usize, const B: usize> Uint<W, B> {
358    fn new() -> Self {
359        Self { words: [0u64; W] }
360    }
361
362    /// Decodes an integer from big-endian format.
363    fn from_be_bytes(b: &[u8; B]) -> Self {
364        let mut z = [0u64; W];
365        for (bytes, word) in b.chunks_exact(8).rev().zip(&mut z) {
366            *word = u64::from_be_bytes(bytes.try_into().expect("len == 8"));
367        }
368        Self { words: z }
369    }
370
371    /// Encodes the integer in big-endian format.
372    fn to_be_bytes(&self) -> [u8; B] {
373        let mut b = [0u8; B];
374        for (bytes, word) in b.chunks_exact_mut(8).rev().zip(self.words) {
375            bytes.copy_from_slice(&word.to_be_bytes());
376        }
377        b
378    }
379
380    /// Reports whether `self == 0.`
381    fn is_zero(&self) -> bool {
382        for x in self.words {
383            if x != 0 {
384                return false;
385            }
386        }
387        true
388    }
389
390    /// Sets `*self = self*y + r` and reports whether the result
391    /// overflowed.
392    fn fma(&mut self, y: u64, r: u64) -> bool {
393        let mut c = r;
394        for x in &mut self.words {
395            (c, *x) = mul_add_ww(*x, y, c);
396        }
397        c == 0
398    }
399
400    /// Computes `(q, r) = self/(58^10)` such that
401    ///
402    /// ```text
403    /// q = x/y
404    /// r = x - y*q
405    /// ```
406    ///
407    /// and sets `*self = q`.
408    fn quo_radix(&mut self) -> u64 {
409        /// 58^10
410        const RADIX: u64 = 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58;
411        // Reciprocal of `RADIX`.
412        const REC: u64 = 0x568df8b76cbf212c;
413
414        let mut r = 0;
415        for x in self.words.iter_mut().rev() {
416            (*x, r) = div_ww(r, *x, RADIX, REC);
417        }
418        r
419    }
420}
421
422impl<const W: usize, const B: usize> Ord for Uint<W, B> {
423    fn cmp(&self, other: &Self) -> Ordering {
424        // Compare words high to low.
425        self.words.iter().rev().cmp(other.words.iter().rev())
426    }
427}
428
429impl<const W: usize, const B: usize> PartialOrd for Uint<W, B> {
430    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
431        Some(Ord::cmp(self, other))
432    }
433}
434
435#[cfg(test)]
436mod test {
437    use std::io::Read;
438
439    use flate2::bufread::GzDecoder;
440
441    use super::*;
442
443    #[derive(Debug, serde_derive::Deserialize)]
444    struct TestCase {
445        input: String,
446        output: String,
447    }
448    macro_rules! impl_test {
449        ($name:ident, $type:ident) => {
450            #[test]
451            fn $name() {
452                const TEST_CASES: &[u8] =
453                    include_bytes!(concat!("../testdata/", stringify!($name), ".json.gz"));
454                let tests: Vec<TestCase> = serde_json::from_slice(
455                    &GzDecoder::new(TEST_CASES)
456                        .bytes()
457                        .collect::<Result<Vec<_>, _>>()
458                        .unwrap(),
459                )
460                .unwrap();
461                for (i, tc) in tests.iter().enumerate() {
462                    let test = format!("test case {i}");
463                    let input = hex::decode(&tc.input)
464                        .expect(&test)
465                        .try_into()
466                        .expect(&test);
467
468                    let got = $type::encode(&input);
469                    // TODO(jdygert): Update test data?
470                    let padded = format!("{:1>width$}", tc.output, width = $type::B58_SIZE);
471                    assert_eq!(got.as_str(), padded.as_str(), "{test}");
472
473                    let got = $type::decode(&got).expect(&test);
474                    assert_eq!(got.as_ref(), input, "{test}");
475                }
476            }
477        };
478    }
479    impl_test!(test_string16, String16);
480    impl_test!(test_string64, String64);
481
482    #[test]
483    fn test_from_str() {
484        let str = String16::from_str("abcd").unwrap();
485        assert_eq!(str.len(), 22);
486        assert_eq!(str, "111111111111111111abcd");
487
488        assert_eq!(str, String16::from_str(str.as_str()).unwrap());
489    }
490}