From 47c346be717df8b6cb18bac45756320d8f235bdd Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 24 Aug 2014 23:03:47 -0700 Subject: [PATCH] Add base58 decode/encode functionality --- src/util/base58.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++ src/util/hash.rs | 12 +++ src/util/mod.rs | 1 + 3 files changed, 221 insertions(+) create mode 100644 src/util/base58.rs diff --git a/src/util/base58.rs b/src/util/base58.rs new file mode 100644 index 0000000..c4d0690 --- /dev/null +++ b/src/util/base58.rs @@ -0,0 +1,208 @@ +// Rust Bitcoin Library +// Written in 2014 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Base58 encoder and decoder + +use std::io::extensions::{u64_to_le_bytes, u64_from_be_bytes}; +use std::string; + +use util::hash::Sha256dHash; + +/// An error that might occur during base58 decoding +#[deriving(Show, PartialEq, Eq, Clone)] +pub enum Base58Error { + /// Invalid character encountered + BadByte(u8), + /// Checksum was not correct (expected, actual) + BadChecksum(u32, u32), + /// Checked data was less than 4 bytes + TooShort(uint) +} + +static BASE58_CHARS: &'static [u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +static BASE58_DIGITS: [Option, ..128] = [ + None, None, None, None, None, None, None, None, // 0-7 + None, None, None, None, None, None, None, None, // 8-15 + None, None, None, None, None, None, None, None, // 16-23 + None, None, None, None, None, None, None, None, // 24-31 + None, None, None, None, None, None, None, None, // 32-39 + None, None, None, None, None, None, None, None, // 40-47 + None, Some(0), Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), // 48-55 + Some(7), Some(8), None, None, None, None, None, None, // 56-63 + None, Some(9), Some(10), Some(11), Some(12), Some(13), Some(14), Some(15), // 64-71 + Some(16), None, Some(17), Some(18), Some(19), Some(20), Some(21), None, // 72-79 + Some(22), Some(23), Some(24), Some(25), Some(26), Some(27), Some(28), Some(29), // 80-87 + Some(30), Some(31), Some(32), None, None, None, None, None, // 88-95 + None, Some(33), Some(34), Some(35), Some(36), Some(37), Some(38), Some(39), // 96-103 + Some(40), Some(41), Some(42), Some(43), None, Some(44), Some(45), Some(46), // 104-111 + Some(47), Some(48), Some(49), Some(50), Some(51), Some(52), Some(53), Some(54), // 112-119 + Some(55), Some(56), Some(57), None, None, None, None, None, // 120-127 +]; + +/// Trait for objects which can be read as base58 +pub trait FromBase58 { + /// Obtain an object from its base58 encoding + fn from_base58(&str) -> Result; + + /// Obtain an object from its base58check encoding + fn from_base58check(&str) -> Result; +} + +/// Trait for objects which can be written as base58 +pub trait ToBase58 { + /// Obtain a string with the base58 encoding of the object + fn to_base58(&self) -> String; + + /// Obtain a string with the base58check encoding of the object + /// (Tack the first 4 256-digits of the object's Bitcoin hash onto the end.) + fn to_base58check(&self) -> String; +} + +impl<'a> ToBase58 for &'a [u8] { + fn to_base58(&self) -> String { + // 7/5 is just over log_58(256) + let mut scratch = Vec::from_elem(1 + self.len() * 7 / 5, 0u8); + // Build in base 58 + for &d256 in self.iter() { + // Compute "X = X * 256 + next_digit" in base 58 + let mut carry = d256 as u32; + for d58 in scratch.mut_iter().rev() { + carry += *d58 as u32 << 8; + *d58 = (carry % 58) as u8; + carry /= 58; + } + assert_eq!(carry, 0); + } + + // Unsafely translate the bytes to a utf8 string + unsafe { + // Copy leading zeroes directly + let mut ret = string::raw::from_utf8(self.iter().take_while(|&&x| x == 0) + .map(|_| BASE58_CHARS[0]) + .collect()); + // Copy rest of string + ret.as_mut_vec().extend(scratch.move_iter().skip_while(|&x| x == 0) + .map(|x| BASE58_CHARS[x as uint])); + ret + } + } + + fn to_base58check(&self) -> String { + let checksum = Sha256dHash::from_data(*self).into_le().low_u32(); + u64_to_le_bytes(checksum as u64, 4, |ck| Vec::from_slice(*self).append(ck).to_base58()) + } +} + +impl ToBase58 for Vec { + fn to_base58(&self) -> String { + self.as_slice().to_base58() + } + + fn to_base58check(&self) -> String { + self.as_slice().to_base58check() + } +} + +impl FromBase58 for Vec { + fn from_base58(data: &str) -> Result, Base58Error> { + // 11/15 is just over log_256(58) + let mut scratch = Vec::from_elem(1 + data.len() * 11 / 15, 0u8); + // Build in base 256 + for d58 in data.bytes() { + // Compute "X = X * 58 + next_digit" in base 256 + if d58 as uint > BASE58_DIGITS.len() { + return Err(BadByte(d58)); + } + let mut carry = match BASE58_DIGITS[d58 as uint] { + Some(d58) => d58 as u32, + None => { return Err(BadByte(d58)); } + }; + for d256 in scratch.mut_iter().rev() { + carry += *d256 as u32 * 58; + *d256 = carry as u8; + carry /= 256; + } + assert_eq!(carry, 0); + } + + // Copy leading zeroes directly + let mut ret: Vec = data.bytes().take_while(|&x| x == BASE58_CHARS[0]) + .map(|_| 0) + .collect(); + // Copy rest of string + ret.extend(scratch.move_iter().skip_while(|&x| x == 0)); + Ok(ret) + } + + fn from_base58check(data: &str) -> Result, Base58Error> { + let mut ret: Vec = try!(FromBase58::from_base58(data)); + if ret.len() < 4 { + return Err(TooShort(ret.len())); + } + let ck_start = ret.len() - 4; + let expected = Sha256dHash::from_data(ret.slice_to(ck_start)).into_le().low_u32(); + let actual = Int::from_be(u64_from_be_bytes(ret.as_slice(), ck_start, 4) as u32); + if expected != actual { + return Err(BadChecksum(expected, actual)); + } + + ret.truncate(ck_start); + Ok(ret) + } +} + + +#[cfg(test)] +mod tests { + use serialize::hex::FromHex; + + use super::ToBase58; + use super::FromBase58; + + #[test] + fn test_base58_encode() { + // Basics + assert_eq!([0].as_slice().to_base58().as_slice(), "1"); + assert_eq!([1].as_slice().to_base58().as_slice(), "2"); + assert_eq!([58].as_slice().to_base58().as_slice(), "21"); + assert_eq!([13, 36].as_slice().to_base58().as_slice(), "211"); + + // Leading zeroes + assert_eq!([0, 13, 36].as_slice().to_base58().as_slice(), "1211"); + assert_eq!([0, 0, 0, 0, 13, 36].as_slice().to_base58().as_slice(), "1111211"); + + // Addresses + assert_eq!("00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap().to_base58check().as_slice(), + "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"); + } + + #[test] + fn test_base58_decode() { + // Basics + assert_eq!(FromBase58::from_base58("1"), Ok(vec![0u8])); + assert_eq!(FromBase58::from_base58("2"), Ok(vec![1u8])); + assert_eq!(FromBase58::from_base58("21"), Ok(vec![58u8])); + assert_eq!(FromBase58::from_base58("211"), Ok(vec![13u8, 36])); + + // Leading zeroes + assert_eq!(FromBase58::from_base58("1211"), Ok(vec![0u8, 13, 36])); + assert_eq!(FromBase58::from_base58("111211"), Ok(vec![0u8, 0, 0, 13, 36])); + + // Addresses + assert_eq!(FromBase58::from_base58check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"), + Ok("00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap())) + } +} + diff --git a/src/util/hash.rs b/src/util/hash.rs index 9b356fe..59d006e 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -207,6 +207,18 @@ impl Sha256dHash { } ret } + + /// Returns a view into the hash + pub fn as_slice<'a>(&'a self) -> &'a [u8] { + let &Sha256dHash(ref data) = self; + data.as_slice() + } + + /// Returns a view of the first `n` bytes of the hash + pub fn slice_to<'a>(&'a self, n: uint) -> &'a [u8] { + let &Sha256dHash(ref data) = self; + data.slice_to(n) + } } impl Clone for Sha256dHash { diff --git a/src/util/mod.rs b/src/util/mod.rs index e47bb5a..167ae69 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -16,6 +16,7 @@ //! //! Functions needed by all parts of the Bitcoin library +pub mod base58; pub mod error; pub mod hash; pub mod iter;