#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![doc(issue_tracker_base_url = "https://github.com/hacer-bark/base58-turbo/issues/")]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(unused_qualifications)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "serde")]
pub mod serde;
mod decode;
mod encode;
use decode::decode_slice_unsafe;
use encode::encode_slice_unsafe;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidCharacter,
BufferTooSmall,
InputTooBig,
WrongAlphabet,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::InvalidCharacter => write!(f, "invalid character in base58 string"),
Error::BufferTooSmall => write!(f, "output buffer too small"),
Error::InputTooBig => write!(f, "input data too big"),
Error::WrongAlphabet => write!(f, "input alphabet has duplicate chars"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub alphabet: [u8; 58],
pub decode_map: [u8; 256],
pub lut_58_squared: [u16; 3364],
}
impl Config {
pub const fn new(alphabet: &[u8; 58]) -> Result<Self, Error> {
let mut map = [255u8; 256];
let mut i = 0;
while i < 58 {
let byte = alphabet[i];
if map[byte as usize] != 255 {
return Err(Error::WrongAlphabet);
}
map[byte as usize] = i as u8;
i += 1;
}
Ok(Self {
alphabet: *alphabet,
decode_map: map,
lut_58_squared: gen_lut_squared(alphabet),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Engine {
config: Config,
}
#[cfg(feature = "serde")]
impl ::serde::Serialize for Config {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
let alpha_str =
core::str::from_utf8(&self.alphabet).map_err(::serde::ser::Error::custom)?;
serializer.serialize_str(alpha_str)
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for Config {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
struct AlphabetVisitor;
impl<'de> ::serde::de::Visitor<'de> for AlphabetVisitor {
type Value = Config;
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("a 58-character Base58 alphabet string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
let bytes = v.as_bytes();
if bytes.len() != 58 {
return Err(E::custom("expected exactly 58-byte alphabet"));
}
let mut alpha = [0u8; 58];
alpha.copy_from_slice(bytes);
Config::new(&alpha).map_err(E::custom)
}
}
deserializer.deserialize_str(AlphabetVisitor)
}
}
#[cfg(feature = "serde")]
impl ::serde::Serialize for Engine {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
self.config.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for Engine {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
Config::deserialize(deserializer).map(|config| Engine { config })
}
}
pub const BITCOIN: Engine =
match Engine::new(b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") {
Ok(e) => e,
Err(_) => panic!("Invalid Bitcoin alphabet definition"),
};
pub const MONERO: Engine =
match Engine::new(b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") {
Ok(e) => e,
Err(_) => panic!("Invalid Monero alphabet definition"),
};
pub const RIPPLE: Engine =
match Engine::new(b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") {
Ok(e) => e,
Err(_) => panic!("Invalid Ripple alphabet definition"),
};
pub const FLICKR: Engine =
match Engine::new(b"123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ") {
Ok(e) => e,
Err(_) => panic!("Invalid Flickr alphabet definition"),
};
const fn gen_lut_squared(alphabet: &[u8; 58]) -> [u16; 3364] {
let mut table = [0u16; 3364];
let mut i = 0;
while i < 3364 {
let c1 = alphabet[i / 58];
let c2 = alphabet[i % 58];
table[i] = ((c1 as u16) << 8) | (c2 as u16);
i += 1;
}
table
}
impl Engine {
pub const fn new(alphabet: &[u8; 58]) -> Result<Self, Error> {
match Config::new(alphabet) {
Ok(c) => Ok(Self { config: c }),
Err(e) => Err(e),
}
}
#[inline(always)]
pub const fn config(&self) -> &Config {
&self.config
}
#[inline]
#[must_use]
pub const fn encoded_len(&self, input_len: usize) -> usize {
(input_len.saturating_mul(137) / 100).saturating_add(1)
}
#[inline]
#[must_use]
pub const fn decoded_len(&self, input_len: usize) -> usize {
input_len
}
#[inline]
pub fn encode_into<T: AsRef<[u8]>>(&self, input: T, output: &mut [u8]) -> Result<usize, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(0);
}
if input.len() > 1024 {
return Err(Error::InputTooBig);
}
let req_len = self.encoded_len(input.len());
if output.len() < req_len {
return Err(Error::BufferTooSmall);
}
let actual_len = unsafe { encode_slice_unsafe(input, output.as_mut_ptr(), &self.config) };
Ok(actual_len)
}
#[inline]
pub fn decode_into<T: AsRef<[u8]>>(&self, input: T, output: &mut [u8]) -> Result<usize, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(0);
}
if input.len() > 2048 {
return Err(Error::InputTooBig);
}
let req_len = self.decoded_len(input.len());
if output.len() < req_len {
return Err(Error::BufferTooSmall);
}
unsafe { decode_slice_unsafe(input, output, &self.config) }
}
#[inline]
#[cfg(feature = "std")]
pub fn encode<T: AsRef<[u8]>>(&self, input: T) -> Result<String, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(String::new());
}
if input.len() > 1024 {
return Err(Error::InputTooBig);
}
let max_len = self.encoded_len(input.len());
let mut out = Vec::with_capacity(max_len);
#[allow(clippy::uninit_vec)]
unsafe {
out.set_len(max_len);
}
match self.encode_into(input, &mut out) {
Ok(actual_len) => {
unsafe {
out.set_len(actual_len);
}
unsafe { Ok(String::from_utf8_unchecked(out)) }
}
Err(_) => {
unsafe {
out.set_len(0);
}
panic!("Base58 encoding failed due to insufficient buffer (logic error).");
}
}
}
#[inline]
#[cfg(feature = "std")]
pub fn decode<T: AsRef<[u8]>>(&self, input: T) -> Result<Vec<u8>, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(Vec::new());
}
if input.len() > 2048 {
return Err(Error::InputTooBig);
}
let max_len = self.decoded_len(input.len());
let mut out = Vec::with_capacity(max_len);
#[allow(clippy::uninit_vec)]
unsafe {
out.set_len(max_len);
}
match self.decode_into(input, &mut out) {
Ok(actual_len) => {
unsafe {
out.set_len(actual_len);
}
Ok(out)
}
Err(e) => {
unsafe {
out.set_len(0);
}
Err(e)
}
}
}
}
#[cfg(all(test, miri))]
mod lib_miri_coverage {
use super::*;
#[test]
fn miri_engine_lifecycle() {
let alphabet = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
let engine = Engine::new(alphabet).unwrap();
let data = b"Miri Test Data";
let encoded = engine.encode(data).unwrap();
let decoded = engine.decode(&encoded).unwrap();
assert_eq!(data, decoded.as_slice());
}
#[test]
fn miri_all_predefined_engines() {
let engines = [BITCOIN, MONERO, RIPPLE, FLICKR];
let data = b"test";
for engine in engines {
let encoded = engine.encode(data).unwrap();
let decoded = engine.decode(&encoded).unwrap();
assert_eq!(data, decoded.as_slice());
}
}
#[test]
fn miri_config_errors() {
let alphabet = [b'a'; 58];
assert!(Config::new(&alphabet).is_err());
}
}