use bitcoin_hashes::{sha256d::Hash as Sha256d, Hash};
use crate::*;
const BASE58_CHARS: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
#[rustfmt::skip]
const BASE58_DIGITS: [Option<u8>; 128] = [
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(0), Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), None, None, None, None, None, None, None, Some(9), Some(10), Some(11), Some(12), Some(13), Some(14), Some(15), Some(16), None, Some(17), Some(18), Some(19), Some(20), Some(21), None, Some(22), Some(23), Some(24), Some(25), Some(26), Some(27), Some(28), Some(29), Some(30), Some(31), Some(32), None, None, None, None, None, None, Some(33), Some(34), Some(35), Some(36), Some(37), Some(38), Some(39), Some(40), Some(41), Some(42), Some(43), None, Some(44), Some(45), Some(46), Some(47), Some(48), Some(49), Some(50), Some(51), Some(52), Some(53), Some(54), Some(55), Some(56), Some(57), None, None, None, None, None, ];
fn from_base58_str(data: &str) -> Result<Vec<u8>, Base58Error> {
let mut scratch = vec![0u8; 1 + data.len() * 11 / 15];
for d58 in data.bytes() {
if d58 as usize > BASE58_DIGITS.len() {
return Err(Base58Error::InvalidChar(d58 as char));
}
let mut carry = match BASE58_DIGITS[d58 as usize] {
Some(d58) => u32::from(d58),
None => {
return Err(Base58Error::InvalidChar(d58 as char));
}
};
for d256 in scratch.iter_mut().rev() {
carry += u32::from(*d256) * 58;
*d256 = carry as u8;
carry /= 256;
}
assert_eq!(carry, 0);
}
let mut ret: Vec<u8> = data
.bytes()
.take_while(|&x| x == BASE58_CHARS[0])
.map(|_| 0)
.collect();
ret.extend(scratch.into_iter().skip_while(|&x| x == 0));
Ok(ret)
}
fn to_base58_str(data: &[u8]) -> String {
let mut ret = Vec::with_capacity(data.len());
let mut leading_zero_count = 0;
let mut leading_zeroes = true;
for d256 in data {
let mut carry = *d256 as usize;
if leading_zeroes && carry == 0 {
leading_zero_count += 1;
} else {
leading_zeroes = false;
}
for ch in ret.iter_mut() {
let new_ch = *ch as usize * 256 + carry;
*ch = (new_ch % 58) as u8;
carry = new_ch / 58;
}
while carry > 0 {
ret.push((carry % 58) as u8);
carry /= 58;
}
}
for _ in 0..leading_zero_count {
ret.push(0);
}
let out: String = ret
.iter()
.rev()
.map(|ch| BASE58_CHARS[*ch as usize] as char)
.collect();
out
}
pub struct Base58Codec;
impl AddressCodec for Base58Codec {
type Error = Base58Error;
fn encode(raw: &[u8], hash_type: HashType, network: Network) -> Result<String, Self::Error> {
let addr_type_byte = match (hash_type, network) {
(HashType::Key, Network::Main) => 0x00,
(HashType::Key, Network::Test) => 0x6f,
(HashType::Key, Network::Regtest) => 0x6f,
(HashType::Script, Network::Main) => 0x05,
(HashType::Script, Network::Test) => 0xc4,
(HashType::Script, Network::Regtest) => 0xc4,
(HashType::Account, Network::Main) => 0x06,
(HashType::Account, Network::Test) => 0xc5,
(HashType::Account, Network::Regtest) => 0xc5,
};
let mut body = Vec::with_capacity(raw.len() + 5);
body.push(addr_type_byte);
body.extend(raw);
let checksum = Sha256d::hash(&body);
body.extend(&checksum[0..4]);
Ok(to_base58_str(&body))
}
fn decode(addr_str: &str) -> Result<Address, Self::Error> {
let raw = from_base58_str(addr_str)?;
let length = raw.len();
if length != 25 {
return Err(Base58Error::InvalidLength(length));
}
let version_byte = raw[0];
let (network, hash_type) = match version_byte {
0x00 => (Network::Main, HashType::Key),
0x05 => (Network::Main, HashType::Script),
0x6f => (Network::Test, HashType::Key),
0xc4 => (Network::Test, HashType::Script),
_ => return Err(Base58Error::InvalidVersion(version_byte)),
};
let payload = &raw[0..raw.len() - 4];
let checksum_actual = &raw[raw.len() - 4..];
let checksum_expected = &Sha256d::hash(payload)[0..4];
if checksum_expected != checksum_actual {
return Err(Base58Error::ChecksumFailed {
expected: checksum_expected.to_vec(),
actual: checksum_actual.to_vec(),
});
}
let body = payload[1..].to_vec();
Ok(Address {
scheme: Scheme::Base58,
body,
hash_type,
network,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin_hashes::hash160::Hash as Hash160;
use hex;
#[test]
fn to_legacyaddr() {
let pubkey_hex = "04005937fd439b3c19014d5f328df8c7ed514eaaf41c1980b8aeab461dffb23fbf3317e42395db24a52ce9fc947d9c22f54dc3217c8b11dfc7a09c59e0dca591d3";
let pubkeyhash = Hash160::hash(&hex::decode(pubkey_hex).unwrap()).to_vec();
let legacyaddr = Base58Codec::encode(&pubkeyhash, HashType::Key, Network::Main).unwrap();
assert!(legacyaddr == "1NM2HFXin4cEQRBLjkNZAS98qLX9JKzjKn");
}
#[test]
fn from_legacyaddr() {
let legacyaddr = "1NM2HFXin4cEQRBLjkNZAS98qLX9JKzjKn";
let result = Base58Codec::decode(legacyaddr).unwrap();
let hash160 = result.as_ref();
assert!(hex::encode(hash160) == "ea2407829a5055466b27784cde8cf463167946bf");
}
#[test]
fn from_legacyaddr_errors() {
assert!(Base58Codec::decode("0").is_err());
assert!(Base58Codec::decode("1000000000000000000000000000000000").is_err());
}
}