bs58/
decode.rs

1//! Functions for decoding Base58 encoded strings.
2
3use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8use crate::Check;
9#[cfg(any(feature = "check", feature = "cb58"))]
10use crate::CHECKSUM_LEN;
11
12use crate::Alphabet;
13
14/// A builder for setting up the alphabet and output of a base58 decode.
15///
16/// See the documentation for [`bs58::decode`](crate::decode()) for a more
17/// high level view of how to use this.
18#[allow(missing_debug_implementations)]
19pub struct DecodeBuilder<'a, I: AsRef<[u8]>> {
20    input: I,
21    alpha: &'a Alphabet,
22    check: Check,
23}
24
25/// A specialized [`Result`](core::result::Result) type for [`bs58::decode`](module@crate::decode)
26pub type Result<T> = core::result::Result<T, Error>;
27
28/// Errors that could occur when decoding a Base58 encoded string.
29#[derive(Copy, Clone, Debug, Eq, PartialEq)]
30#[non_exhaustive]
31pub enum Error {
32    /// The output buffer was too small to contain the entire input.
33    BufferTooSmall,
34
35    /// The input contained a character that was not part of the current Base58
36    /// alphabet.
37    InvalidCharacter {
38        /// The unexpected character.
39        character: char,
40        /// The (byte) index in the input string the character was at.
41        index: usize,
42    },
43
44    /// The input contained a multi-byte (or non-utf8) character which is
45    /// unsupported by this Base58 decoder.
46    NonAsciiCharacter {
47        /// The (byte) index in the input string the start of the character was
48        /// at.
49        index: usize,
50    },
51
52    #[cfg(any(feature = "check", feature = "cb58"))]
53    /// The checksum did not match the payload bytes
54    InvalidChecksum {
55        ///The given checksum
56        checksum: [u8; CHECKSUM_LEN],
57        ///The checksum calculated for the payload
58        expected_checksum: [u8; CHECKSUM_LEN],
59    },
60
61    #[cfg(any(feature = "check", feature = "cb58"))]
62    /// The version did not match the payload bytes
63    InvalidVersion {
64        ///The given version
65        ver: u8,
66        ///The expected version
67        expected_ver: u8,
68    },
69
70    #[cfg(any(feature = "check", feature = "cb58"))]
71    ///Not enough bytes to have both a checksum and a payload (less than to CHECKSUM_LEN)
72    NoChecksum,
73}
74
75/// Represents a buffer that can be decoded into. See [`DecodeBuilder::onto`] and the provided
76/// implementations for more details.
77pub trait DecodeTarget {
78    /// Decodes into this buffer, provides the maximum length for implementations that wish to
79    /// preallocate space, along with a function that will write bytes into the buffer and return
80    /// the length written to it.
81    fn decode_with(
82        &mut self,
83        max_len: usize,
84        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
85    ) -> Result<usize>;
86}
87
88impl<T: DecodeTarget + ?Sized> DecodeTarget for &mut T {
89    fn decode_with(
90        &mut self,
91        max_len: usize,
92        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
93    ) -> Result<usize> {
94        T::decode_with(self, max_len, f)
95    }
96}
97
98#[cfg(feature = "alloc")]
99impl DecodeTarget for Vec<u8> {
100    fn decode_with(
101        &mut self,
102        max_len: usize,
103        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
104    ) -> Result<usize> {
105        let original = self.len();
106        self.resize(original + max_len, 0);
107        let len = f(&mut self[original..])?;
108        self.truncate(original + len);
109        Ok(len)
110    }
111}
112
113#[cfg(feature = "smallvec")]
114impl<A: smallvec::Array<Item = u8>> DecodeTarget for smallvec::SmallVec<A> {
115    /// Decodes data into a [`smallvec::SmallVec`].
116    fn decode_with(
117        &mut self,
118        max_len: usize,
119        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
120    ) -> Result<usize> {
121        let original = self.len();
122        self.resize(original + max_len, 0);
123        let len = f(&mut self[original..])?;
124        self.truncate(original + len);
125        Ok(len)
126    }
127}
128
129#[cfg(feature = "tinyvec")]
130impl<A: tinyvec::Array<Item = u8>> DecodeTarget for tinyvec::ArrayVec<A> {
131    fn decode_with(
132        &mut self,
133        max_len: usize,
134        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
135    ) -> Result<usize> {
136        let _ = max_len;
137        let original = self.len();
138        let len = f(self.grab_spare_slice_mut())?;
139        self.set_len(original + len);
140        Ok(len)
141    }
142}
143
144#[cfg(feature = "tinyvec")]
145impl DecodeTarget for tinyvec::SliceVec<'_, u8> {
146    fn decode_with(
147        &mut self,
148        max_len: usize,
149        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
150    ) -> Result<usize> {
151        let _ = max_len;
152        let original = self.len();
153        let len = f(self.grab_spare_slice_mut())?;
154        self.set_len(original + len);
155        Ok(len)
156    }
157}
158
159#[cfg(all(feature = "tinyvec", feature = "alloc"))]
160impl<A: tinyvec::Array<Item = u8>> DecodeTarget for tinyvec::TinyVec<A> {
161    fn decode_with(
162        &mut self,
163        max_len: usize,
164        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
165    ) -> Result<usize> {
166        let original = self.len();
167        self.resize(original + max_len, 0);
168        let len = f(&mut self[original..])?;
169        self.truncate(original + len);
170        Ok(len)
171    }
172}
173
174impl DecodeTarget for [u8] {
175    fn decode_with(
176        &mut self,
177        max_len: usize,
178        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
179    ) -> Result<usize> {
180        let _ = max_len;
181        f(&mut *self)
182    }
183}
184
185impl<const N: usize> DecodeTarget for [u8; N] {
186    fn decode_with(
187        &mut self,
188        max_len: usize,
189        f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
190    ) -> Result<usize> {
191        let _ = max_len;
192        f(&mut *self)
193    }
194}
195
196impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
197    /// Setup decoder for the given string using the given alphabet.
198    /// Preferably use [`bs58::decode`](crate::decode()) instead of this directly.
199    pub const fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
200        DecodeBuilder {
201            input,
202            alpha,
203            check: Check::Disabled,
204        }
205    }
206
207    /// Setup decoder for the given string using default prepared alphabet.
208    pub(crate) const fn from_input(input: I) -> DecodeBuilder<'static, I> {
209        DecodeBuilder {
210            input,
211            alpha: Alphabet::DEFAULT,
212            check: Check::Disabled,
213        }
214    }
215
216    /// Change the alphabet that will be used for decoding.
217    ///
218    /// # Examples
219    ///
220    /// ```rust
221    /// assert_eq!(
222    ///     vec![0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
223    ///     bs58::decode("he11owor1d")
224    ///         .with_alphabet(bs58::Alphabet::RIPPLE)
225    ///         .into_vec()?);
226    /// # Ok::<(), bs58::decode::Error>(())
227    /// ```
228    pub const fn with_alphabet(mut self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
229        self.alpha = alpha;
230        self
231    }
232
233    /// Expect and check checksum using the [Base58Check][] algorithm when
234    /// decoding.
235    ///
236    /// Optional parameter for version byte. If provided, the version byte will
237    /// be used in verification.
238    ///
239    /// [Base58Check]: https://en.bitcoin.it/wiki/Base58Check_encoding
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// assert_eq!(
245    ///     vec![0x2d, 0x31],
246    ///     bs58::decode("PWEu9GGN")
247    ///         .with_check(None)
248    ///         .into_vec()?);
249    /// # Ok::<(), bs58::decode::Error>(())
250    /// ```
251    #[cfg(feature = "check")]
252    pub fn with_check(self, expected_ver: Option<u8>) -> DecodeBuilder<'a, I> {
253        let check = Check::Enabled(expected_ver);
254        DecodeBuilder { check, ..self }
255    }
256
257    /// Expect and check checksum using the [CB58][] algorithm when
258    /// decoding.
259    ///
260    /// Optional parameter for version byte. If provided, the version byte will
261    /// be used in verification.
262    ///
263    /// [CB58]: https://support.avax.network/en/articles/4587395-what-is-cb58
264    ///
265    /// # Examples
266    ///
267    /// ```rust
268    /// assert_eq!(
269    ///     vec![0x2d, 0x31],
270    ///     bs58::decode("PWHVMzdR")
271    ///         .as_cb58(None)
272    ///         .into_vec()?);
273    /// # Ok::<(), bs58::decode::Error>(())
274    /// ```
275    #[cfg(feature = "cb58")]
276    pub fn as_cb58(self, expected_ver: Option<u8>) -> DecodeBuilder<'a, I> {
277        let check = Check::CB58(expected_ver);
278        DecodeBuilder { check, ..self }
279    }
280    /// Decode into a new vector of bytes.
281    ///
282    /// See the documentation for [`bs58::decode`](crate::decode()) for an
283    /// explanation of the errors that may occur.
284    ///
285    /// # Examples
286    ///
287    /// ```rust
288    /// assert_eq!(
289    ///     vec![0x04, 0x30, 0x5e, 0x2b, 0x24, 0x73, 0xf0, 0x58],
290    ///     bs58::decode("he11owor1d").into_vec()?);
291    /// # Ok::<(), bs58::decode::Error>(())
292    /// ```
293    ///
294    #[cfg(feature = "alloc")]
295    pub fn into_vec(self) -> Result<Vec<u8>> {
296        let mut output = Vec::new();
297        self.onto(&mut output)?;
298        Ok(output)
299    }
300
301    /// Decode into the given buffer.
302    ///
303    /// Returns the length written into the buffer.
304    ///
305    /// If the buffer is resizeable it will be extended and the new data will be written to the end
306    /// of it.
307    ///
308    /// If the buffer is not resizeable bytes will be written from the beginning and bytes after
309    /// the final encoded byte will not be touched.
310    ///
311    /// See the documentation for [`bs58::decode`](crate::decode()) for an
312    /// explanation of the errors that may occur.
313    ///
314    /// # Examples
315    ///
316    /// ## `Vec<u8>`
317    ///
318    /// ```rust
319    /// let mut output = b"hello ".to_vec();
320    /// assert_eq!(5, bs58::decode("EUYUqQf").onto(&mut output)?);
321    /// assert_eq!(b"hello world", output.as_slice());
322    /// # Ok::<(), bs58::decode::Error>(())
323    /// ```
324    ///
325    /// ## `&mut [u8]`
326    ///
327    /// ```rust
328    /// let mut output = b"hello ".to_owned();
329    /// assert_eq!(5, bs58::decode("EUYUqQf").onto(&mut output)?);
330    /// assert_eq!(b"world ", output.as_ref());
331    /// # Ok::<(), bs58::decode::Error>(())
332    /// ```
333    pub fn onto(self, mut output: impl DecodeTarget) -> Result<usize> {
334        let max_decoded_len = self.input.as_ref().len();
335        match self.check {
336            Check::Disabled => output.decode_with(max_decoded_len, |output| {
337                decode_into(self.input.as_ref(), output, self.alpha)
338            }),
339            #[cfg(feature = "check")]
340            Check::Enabled(expected_ver) => output.decode_with(max_decoded_len, |output| {
341                decode_check_into(self.input.as_ref(), output, self.alpha, expected_ver)
342            }),
343            #[cfg(feature = "cb58")]
344            Check::CB58(expected_ver) => output.decode_with(max_decoded_len, |output| {
345                decode_cb58_into(self.input.as_ref(), output, self.alpha, expected_ver)
346            }),
347        }
348    }
349}
350
351/// For `const` compatibility we are restricted to using a concrete input and output type, as
352/// `const` trait implementations and `&mut` are unstable. These methods will eventually be
353/// deprecated once the primary interfaces can be converted into `const fn` directly.
354impl<'a, 'b> DecodeBuilder<'a, &'b [u8]> {
355    /// Decode into a new array.
356    ///
357    /// Returns the decoded array as bytes.
358    ///
359    /// See the documentation for [`bs58::decode`](crate::decode())
360    /// for an explanation of the errors that may occur.
361    ///
362    /// # Examples
363    ///
364    /// ```rust
365    /// const _: () = {
366    ///     let Ok(output) = bs58::decode(b"EUYUqQf".as_slice()).into_array_const::<5>() else {
367    ///         panic!()
368    ///     };
369    ///     assert!(matches!(&output, b"world"));
370    /// };
371    /// ```
372    pub const fn into_array_const<const N: usize>(self) -> Result<[u8; N]> {
373        assert!(
374            matches!(self.check, Check::Disabled),
375            "checksums in const aren't supported (why are you using this API at runtime)",
376        );
377        decode_into_const(self.input, self.alpha)
378    }
379
380    /// [`Self::into_array_const`] but the result will be unwrapped, turning any error into a panic
381    /// message via [`Error::unwrap_const`], as a simple `into_array_const().unwrap()` isn't
382    /// possible yet.
383    ///
384    /// # Examples
385    ///
386    /// ```rust
387    /// const _: () = {
388    ///     let output: [u8; 5] = bs58::decode(b"EUYUqQf".as_slice()).into_array_const_unwrap();
389    ///     assert!(matches!(&output, b"world"));
390    /// };
391    /// ```
392    ///
393    /// ```rust
394    /// const _: () = {
395    ///     assert!(matches!(
396    ///         bs58::decode(b"he11owor1d".as_slice())
397    ///             .with_alphabet(bs58::Alphabet::RIPPLE)
398    ///             .into_array_const_unwrap(),
399    ///         [0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
400    ///     ));
401    /// };
402    /// ```
403    pub const fn into_array_const_unwrap<const N: usize>(self) -> [u8; N] {
404        match self.into_array_const() {
405            Ok(result) => result,
406            Err(err) => err.unwrap_const(),
407        }
408    }
409}
410
411fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result<usize> {
412    let mut index = 0;
413    let zero = alpha.encode[0];
414
415    for (i, c) in input.iter().enumerate() {
416        if *c > 127 {
417            return Err(Error::NonAsciiCharacter { index: i });
418        }
419
420        let mut val = alpha.decode[*c as usize] as usize;
421        if val == 0xFF {
422            return Err(Error::InvalidCharacter {
423                character: *c as char,
424                index: i,
425            });
426        }
427
428        for byte in &mut output[..index] {
429            val += (*byte as usize) * 58;
430            *byte = (val & 0xFF) as u8;
431            val >>= 8;
432        }
433
434        while val > 0 {
435            let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?;
436            *byte = (val & 0xFF) as u8;
437            index += 1;
438            val >>= 8
439        }
440    }
441
442    for _ in input.iter().take_while(|c| **c == zero) {
443        let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?;
444        *byte = 0;
445        index += 1;
446    }
447
448    output[..index].reverse();
449    Ok(index)
450}
451
452#[cfg(feature = "check")]
453fn decode_check_into(
454    input: &[u8],
455    output: &mut [u8],
456    alpha: &Alphabet,
457    expected_ver: Option<u8>,
458) -> Result<usize> {
459    use sha2::{Digest, Sha256};
460
461    let decoded_len = decode_into(input, output, alpha)?;
462    if decoded_len < CHECKSUM_LEN {
463        return Err(Error::NoChecksum);
464    }
465    let checksum_index = decoded_len - CHECKSUM_LEN;
466
467    let expected_checksum = &output[checksum_index..decoded_len];
468
469    let first_hash = Sha256::digest(&output[0..checksum_index]);
470    let second_hash = Sha256::digest(first_hash);
471    let (checksum, _) = second_hash.split_at(CHECKSUM_LEN);
472
473    if checksum == expected_checksum {
474        if let Some(ver) = expected_ver {
475            if output[0] == ver {
476                Ok(checksum_index)
477            } else {
478                Err(Error::InvalidVersion {
479                    ver: output[0],
480                    expected_ver: ver,
481                })
482            }
483        } else {
484            Ok(checksum_index)
485        }
486    } else {
487        let mut a: [u8; CHECKSUM_LEN] = Default::default();
488        a.copy_from_slice(checksum);
489        let mut b: [u8; CHECKSUM_LEN] = Default::default();
490        b.copy_from_slice(expected_checksum);
491        Err(Error::InvalidChecksum {
492            checksum: a,
493            expected_checksum: b,
494        })
495    }
496}
497
498#[cfg(feature = "cb58")]
499fn decode_cb58_into(
500    input: &[u8],
501    output: &mut [u8],
502    alpha: &Alphabet,
503    expected_ver: Option<u8>,
504) -> Result<usize> {
505    use sha2::{Digest, Sha256};
506
507    let decoded_len = decode_into(input, output, alpha)?;
508    if decoded_len < CHECKSUM_LEN {
509        return Err(Error::NoChecksum);
510    }
511    let checksum_index = decoded_len - CHECKSUM_LEN;
512
513    let expected_checksum = &output[checksum_index..decoded_len];
514
515    let hash = Sha256::digest(&output[0..checksum_index]);
516    let (_, checksum) = hash.split_at(hash.len() - CHECKSUM_LEN);
517
518    if checksum == expected_checksum {
519        if let Some(ver) = expected_ver {
520            if output[0] == ver {
521                Ok(checksum_index)
522            } else {
523                Err(Error::InvalidVersion {
524                    ver: output[0],
525                    expected_ver: ver,
526                })
527            }
528        } else {
529            Ok(checksum_index)
530        }
531    } else {
532        let mut a: [u8; CHECKSUM_LEN] = Default::default();
533        a.copy_from_slice(checksum);
534        let mut b: [u8; CHECKSUM_LEN] = Default::default();
535        b.copy_from_slice(expected_checksum);
536        Err(Error::InvalidChecksum {
537            checksum: a,
538            expected_checksum: b,
539        })
540    }
541}
542
543const fn decode_into_const<const N: usize>(input: &[u8], alpha: &Alphabet) -> Result<[u8; N]> {
544    let mut output = [0u8; N];
545    let mut index = 0;
546    let zero = alpha.encode[0];
547
548    let mut i = 0;
549    while i < input.len() {
550        let c = input[i];
551        if c > 127 {
552            return Err(Error::NonAsciiCharacter { index: i });
553        }
554
555        let mut val = alpha.decode[c as usize] as usize;
556        if val == 0xFF {
557            return Err(Error::InvalidCharacter {
558                character: c as char,
559                index: i,
560            });
561        }
562
563        let mut j = 0;
564        while j < index {
565            let byte = output[j];
566            val += (byte as usize) * 58;
567            output[j] = (val & 0xFF) as u8;
568            val >>= 8;
569            j += 1;
570        }
571
572        while val > 0 {
573            if index >= output.len() {
574                return Err(Error::BufferTooSmall);
575            }
576            output[index] = (val & 0xFF) as u8;
577            index += 1;
578            val >>= 8
579        }
580        i += 1;
581    }
582
583    let mut i = 0;
584    while i < input.len() && input[i] == zero {
585        if index >= output.len() {
586            return Err(Error::BufferTooSmall);
587        }
588        output[index] = 0;
589        index += 1;
590        i += 1;
591    }
592
593    // reverse
594    let mut i = 0;
595    let n = index / 2;
596    while i < n {
597        let x = output[i];
598        output[i] = output[index - 1 - i];
599        output[index - 1 - i] = x;
600        i += 1;
601    }
602
603    Ok(output)
604}
605
606#[cfg(feature = "std")]
607impl std::error::Error for Error {}
608
609impl fmt::Display for Error {
610    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
611        match *self {
612            Error::BufferTooSmall => write!(
613                f,
614                "buffer provided to decode base58 encoded string into was too small"
615            ),
616            Error::InvalidCharacter { character, index } => write!(
617                f,
618                "provided string contained invalid character {:?} at byte {}",
619                character, index
620            ),
621            Error::NonAsciiCharacter { index } => write!(
622                f,
623                "provided string contained non-ascii character starting at byte {}",
624                index
625            ),
626            #[cfg(any(feature = "check", feature = "cb58"))]
627            Error::InvalidChecksum {
628                checksum,
629                expected_checksum,
630            } => write!(
631                f,
632                "invalid checksum, calculated checksum: '{:?}', expected checksum: {:?}",
633                checksum, expected_checksum
634            ),
635            #[cfg(any(feature = "check", feature = "cb58"))]
636            Error::InvalidVersion { ver, expected_ver } => write!(
637                f,
638                "invalid version, payload version: '{:?}', expected version: {:?}",
639                ver, expected_ver
640            ),
641            #[cfg(any(feature = "check", feature = "cb58"))]
642            Error::NoChecksum => write!(f, "provided string is too small to contain a checksum"),
643        }
644    }
645}
646
647impl Error {
648    /// Panic with an error message based on this error. This cannot include any of the dynamic
649    /// content because formatting in `const` is not yet possible.
650    pub const fn unwrap_const(self) -> ! {
651        match self {
652            Error::BufferTooSmall => {
653                panic!("buffer provided to decode base58 encoded string into was too small")
654            }
655            Error::InvalidCharacter { .. } => panic!("provided string contained invalid character"),
656            Error::NonAsciiCharacter { .. } => {
657                panic!("provided string contained non-ascii character")
658            }
659            #[cfg(any(feature = "check", feature = "cb58"))]
660            Error::InvalidChecksum { .. } => panic!("invalid checksum"),
661            #[cfg(any(feature = "check", feature = "cb58"))]
662            Error::InvalidVersion { .. } => panic!("invalid version"),
663            #[cfg(any(feature = "check", feature = "cb58"))]
664            Error::NoChecksum => panic!("provided string is too small to contain a checksum"),
665        }
666    }
667}