rust_base58/
base58.rs

1use num::bigint::ToBigUint;
2use num::{BigUint, Zero, One};
3use num::traits::ToPrimitive;
4use std::fmt;
5
6pub use self::FromBase58Error::*;
7
8static BTC_ALPHA: &'static[u8] = b"123456789\
9                                   ABCDEFGHJKLMNPQRSTUVWXYZ\
10                                   abcdefghijkmnopqrstuvwxyz";
11
12static FLICKR_ALPHA: &'static[u8] = b"123456789\
13                                      abcdefghijkmnopqrstuvwxyz\
14                                      ABCDEFGHJKLMNPQRSTUVWXYZ";
15
16/// A trait for converting base58-encoded values
17// TODO: This should incorporate the alphabet used as an associated constant.  However, associated constants are not implemented in Rust yet. There is a
18// PR though: https://github.com/rust-lang/rust/pull/23606
19pub trait FromBase58 {
20    /// Converts the value of `self`, interpreted as base58 encoded data,
21    /// into an owned vector of bytes, returning the vector.
22    fn from_base58(&self) -> Result<Vec<u8>, FromBase58Error>;
23}
24
25
26/// Errors that can occur when decoding a base58-encoded string
27#[derive(Clone, Copy)]
28pub enum FromBase58Error {
29    /// The input contained a character not part of the base58 alphabet
30    InvalidBase58Byte(u8, usize),
31}
32
33impl fmt::Debug for FromBase58Error {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        match *self {
36            InvalidBase58Byte(ch, idx) =>
37                write!(f, "Invalid character '{}' at position {}", ch, idx),
38        }
39    }
40}
41
42impl fmt::Display for FromBase58Error {
43    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44        fmt::Debug::fmt(&self, f)
45    }
46}
47
48
49
50impl FromBase58 for str {
51    fn from_base58(&self) -> Result<Vec<u8>, FromBase58Error> {
52        self.as_bytes().from_base58()
53    }
54}
55
56impl FromBase58 for [u8] {
57    // TODO: fix some of the below when the binary assignment operators +=, *=
58    // are overloadable
59    fn from_base58(&self) -> Result<Vec<u8>, FromBase58Error> {
60        let radix = 58.to_biguint().unwrap();
61        let mut x: BigUint = Zero::zero();
62        let mut rad_mult: BigUint = One::one();
63
64        // Convert the base58 string to a BigUint `x`
65        for (idx, &byte) in self.iter().enumerate().rev() {
66            let first_idx = BTC_ALPHA.iter()
67                                     .enumerate()
68                                     .find(|x| *x.1 == byte)
69                                     .map(|x| x.0);
70            match first_idx {
71                Some(i) => { x = x + i.to_biguint().unwrap() * &rad_mult; },
72                None => return Err(InvalidBase58Byte(self[idx], idx))
73            }
74
75            rad_mult = &rad_mult * &radix;
76        }
77
78        let mut r = Vec::with_capacity(self.len());
79        for _ in self.iter().take_while(|&x| *x == BTC_ALPHA[0]) {
80            r.push(0);
81        }
82        if x > Zero::zero() {
83            // TODO: use append when it becomes stable
84            r.extend(x.to_bytes_be());
85        }
86        Ok(r)
87    }
88}
89
90
91/// A trait for converting a value to base58 encoding.
92pub trait ToBase58 {
93    /// Converts the value of `self` to a base-58 value, returning the owned
94    /// string.
95    fn to_base58(&self) -> String;
96}
97
98impl ToBase58 for [u8] {
99    // This function has to read in the entire byte slice and convert it to a
100    // (big) int before creating the string. There's no way to incrementally read
101    // the slice and create parts of the base58 string. Example:
102    //   [1, 33] should be "5z"
103    //   [1, 34] should be "61"
104    // so by reading "1", no way to know if first character should be 5 or 6
105    // without reading the rest
106    fn to_base58(&self) -> String {
107        let radix = 58.to_biguint().unwrap();
108        let mut x = BigUint::from_bytes_be(&self);
109        let mut ans = vec![];
110        while x > Zero::zero() {
111            let rem = (&x % &radix).to_usize().unwrap();
112            ans.push(BTC_ALPHA[rem]);
113            x = &x / &radix;
114        }
115
116        // take care of leading zeros
117        for _ in self.iter().take_while(|&x| *x == 0) {
118            ans.push(BTC_ALPHA[0]);
119        }
120        ans.reverse();
121        String::from_utf8(ans).unwrap()
122    }
123}
124
125
126#[cfg(test)]
127mod tests {
128    use base58::{FromBase58, ToBase58};
129
130    #[test]
131    fn test_from_base58_basic() {
132        assert_eq!("".from_base58().unwrap(), b"");
133        assert_eq!("Z".from_base58().unwrap(), &[32]);
134        assert_eq!("n".from_base58().unwrap(), &[45]);
135        assert_eq!("q".from_base58().unwrap(), &[48]);
136        assert_eq!("r".from_base58().unwrap(), &[49]);
137        assert_eq!("z".from_base58().unwrap(), &[57]);
138        assert_eq!("4SU".from_base58().unwrap(), &[45, 49]);
139        assert_eq!("4k8".from_base58().unwrap(), &[49, 49]);
140        assert_eq!("ZiCa".from_base58().unwrap(), &[97, 98, 99]);
141        assert_eq!("3mJr7AoUXx2Wqd".from_base58().unwrap(), b"1234598760");
142        assert_eq!("3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f".from_base58().unwrap(), b"abcdefghijklmnopqrstuvwxyz");
143    }
144
145    #[test]
146    fn test_from_base58_bytes() {
147        assert_eq!(b"ZiCa".from_base58().unwrap(), b"abc");
148    }
149
150    #[test]
151    fn test_from_base58_invalid_char() {
152        assert!("0".from_base58().is_err());
153        assert!("O".from_base58().is_err());
154        assert!("I".from_base58().is_err());
155        assert!("l".from_base58().is_err());
156        assert!("3mJr0".from_base58().is_err());
157        assert!("O3yxU".from_base58().is_err());
158        assert!("3sNI".from_base58().is_err());
159        assert!("4kl8".from_base58().is_err());
160        assert!("s!5<".from_base58().is_err());
161        assert!("t$@mX<*".from_base58().is_err());
162    }
163
164    #[test]
165    fn test_from_base58_initial_zeros() {
166        assert_eq!("1ZiCa".from_base58().unwrap(), b"\0abc");
167        assert_eq!("11ZiCa".from_base58().unwrap(), b"\0\0abc");
168        assert_eq!("111ZiCa".from_base58().unwrap(), b"\0\0\0abc");
169        assert_eq!("1111ZiCa".from_base58().unwrap(), b"\0\0\0\0abc");
170    }
171
172    #[test]
173    fn test_to_base58_basic() {
174        assert_eq!(b"".to_base58(), "");
175        assert_eq!(&[32].to_base58(), "Z");
176        assert_eq!(&[45].to_base58(), "n");
177        assert_eq!(&[48].to_base58(), "q");
178        assert_eq!(&[49].to_base58(), "r");
179        assert_eq!(&[57].to_base58(), "z");
180        assert_eq!(&[45, 49].to_base58(), "4SU");
181        assert_eq!(&[49, 49].to_base58(), "4k8");
182        assert_eq!(b"abc".to_base58(), "ZiCa");
183        assert_eq!(b"1234598760".to_base58(), "3mJr7AoUXx2Wqd");
184        assert_eq!(b"abcdefghijklmnopqrstuvwxyz".to_base58(), "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f");
185    }
186
187    #[test]
188    fn test_to_base58_initial_zeros() {
189        assert_eq!(b"\0abc".to_base58(), "1ZiCa");
190        assert_eq!(b"\0\0abc".to_base58(), "11ZiCa");
191        assert_eq!(b"\0\0\0abc".to_base58(), "111ZiCa");
192        assert_eq!(b"\0\0\0\0abc".to_base58(), "1111ZiCa");
193    }
194
195    #[test]
196    fn test_base58_random() {
197        use rand::{thread_rng, Rng};
198
199        for _ in 0..200 {
200            let times = thread_rng().gen_range(1, 100);
201            let v = thread_rng().gen_iter::<u8>().take(times)
202                                .collect::<Vec<_>>();
203            assert_eq!(v.to_base58()
204                        .from_base58()
205                        .unwrap(),
206                       v);
207        }
208    }
209}