koibumi_base58/
lib.rs

1//! This crate is a Base58 encoder/decoder library.
2//!
3//! The library is intended to be used to implement a [Bitmessage](https://bitmessage.org/) address encoder/decoder.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use koibumi_base58 as base58;
9//!
10//! let test = base58::encode(b"hello");
11//! let expected = "Cn8eVZg";
12//! assert_eq!(test, expected);
13//! # Ok::<(), Box<dyn std::error::Error>>(())
14//! ```
15//!
16//! ```rust
17//! use koibumi_base58 as base58;
18//!
19//! let test = base58::decode("Cn8eVZg")?;
20//! let expected = b"hello";
21//! assert_eq!(test, expected);
22//! # Ok::<(), Box<dyn std::error::Error>>(())
23//! ```
24
25#![deny(unsafe_code)]
26#![warn(missing_docs)]
27
28#[macro_use]
29extern crate lazy_static;
30
31use std::{convert::TryInto, fmt};
32
33use num_bigint::{BigUint, ToBigUint};
34use num_integer::Integer;
35use num_traits::Zero;
36
37const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
38const INVALID: u8 = ALPHABET.len() as u8;
39
40lazy_static! {
41    static ref ALPHABET_INDEX: [u8; 0x100] = {
42        let mut index = [INVALID; 0x100];
43        for i in 0..ALPHABET.len() {
44            index[ALPHABET[i] as usize] = i as u8;
45        }
46        index
47    };
48}
49
50/// Encodes byte array into Base58 string.
51///
52/// # Examples
53///
54/// ```rust
55/// use koibumi_base58 as base58;
56///
57/// let test = base58::encode(b"hello");
58/// let expected = "Cn8eVZg";
59/// assert_eq!(test, expected);
60/// # Ok::<(), Box<dyn std::error::Error>>(())
61/// ```
62pub fn encode(bytes: impl AsRef<[u8]>) -> String {
63    let bytes = bytes.as_ref();
64    if bytes.is_empty() {
65        return String::with_capacity(0);
66    }
67    let mut n = BigUint::from_bytes_be(bytes);
68    if n == Zero::zero() {
69        return String::from_utf8(vec![ALPHABET[0]]).unwrap();
70    }
71    let mut list: Vec<u8> = Vec::new();
72    let base = ALPHABET.len().to_biguint().unwrap();
73    while n != Zero::zero() {
74        let (q, r) = n.div_mod_floor(&base);
75        list.push(r.try_into().unwrap());
76        n = q;
77    }
78    let mut s = Vec::new();
79    for i in list.iter().rev() {
80        s.push(ALPHABET[*i as usize]);
81    }
82    String::from_utf8(s).unwrap()
83}
84
85#[test]
86fn test_encode() {
87    assert_eq!(encode(b""), "");
88}
89
90/// Indicates that an invalid Base58 character was found.
91///
92/// This error is used as the error type for the [`decode`] function.
93///
94/// [`decode`]: fn.decode.html
95#[derive(Clone, PartialEq, Eq, Debug)]
96pub struct InvalidCharacter(char);
97
98impl InvalidCharacter {
99    /// Returns the actual character found invalid.
100    pub fn char(&self) -> char {
101        self.0
102    }
103}
104
105impl fmt::Display for InvalidCharacter {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        let ch = self.0;
108        let code = u32::from(ch);
109        if ch.is_control() {
110            write!(f, "invalid character ({:#08x}) found", code)
111        } else {
112            write!(f, "invalid character '{}' ({:#08x}) found", ch, code)
113        }
114    }
115}
116
117impl std::error::Error for InvalidCharacter {}
118
119fn to_num(ch: char) -> Result<u8, InvalidCharacter> {
120    let i = ch as usize;
121    if i > 0xff {
122        return Err(InvalidCharacter(ch));
123    }
124    let v = ALPHABET_INDEX[i];
125    if v == INVALID {
126        Err(InvalidCharacter(ch))
127    } else {
128        Ok(v)
129    }
130}
131
132/// Decodes Base58 string into byte array.
133///
134/// # Examples
135///
136/// ```rust
137/// use koibumi_base58 as base58;
138///
139/// let test = base58::decode("Cn8eVZg")?;
140/// let expected = b"hello";
141/// assert_eq!(test, expected);
142/// # Ok::<(), Box<dyn std::error::Error>>(())
143/// ```
144pub fn decode(s: impl AsRef<str>) -> Result<Vec<u8>, InvalidCharacter> {
145    let s = s.as_ref();
146    if s.is_empty() {
147        return Ok(Vec::with_capacity(0));
148    }
149    let base = ALPHABET.len();
150    let mut n: BigUint = Zero::zero();
151    for c in s.chars() {
152        n *= base;
153        n += to_num(c)?;
154    }
155    Ok(n.to_bytes_be())
156}
157
158#[test]
159fn test_decode() {
160    assert_eq!(decode("").unwrap(), b"");
161}