use bs58;
use base64ct::{
Base64UrlUnpadded,
Encoding,
Error as Base64Error,
};
use thiserror::Error;
const BASE_58_BTC_PREFIX: &str = "z";
const BASE_64_URL_NO_PAD_PREFIX: &str = "u";
pub enum Multibase {
Base58Btc,
Base64UrlNoPad,
}
#[derive(Debug, Error)]
pub enum MultibaseError {
#[error("invalid base string")]
InvalidBaseString,
#[error("unknown base")]
UnknownBase,
#[error(transparent)]
Base58Error(#[from] bs58::decode::Error),
#[error(transparent)]
Base64Error(#[from] Base64Error),
}
impl Multibase {
pub fn encode(self, value: &[u8]) -> String {
match self {
Self::Base58Btc => {
let encoded = bs58::encode(value)
.with_alphabet(bs58::Alphabet::BITCOIN)
.into_string();
format!("{BASE_58_BTC_PREFIX}{encoded}")
},
Self::Base64UrlNoPad => {
let encoded = Base64UrlUnpadded::encode_string(value);
format!("{BASE_64_URL_NO_PAD_PREFIX}{encoded}")
},
}
}
pub fn decode(value: &str) -> Result<(Self, Vec<u8>), MultibaseError> {
let prefix = value.chars().next()
.ok_or(MultibaseError::InvalidBaseString)?;
let encoded_data = &value[prefix.len_utf8()..];
let output = match prefix.to_string().as_str() {
BASE_58_BTC_PREFIX => {
let data = bs58::decode(encoded_data)
.with_alphabet(bs58::Alphabet::BITCOIN)
.into_vec()?;
(Self::Base58Btc, data)
},
BASE_64_URL_NO_PAD_PREFIX => {
let data = Base64UrlUnpadded::decode_vec(encoded_data)?;
(Self::Base64UrlNoPad, data)
},
_ => return Err(MultibaseError::UnknownBase),
};
Ok(output)
}
pub fn decode_exact(self, value: &str) -> Result<Vec<u8>, MultibaseError> {
let (_encoding, data) = Self::decode(value)?;
if !matches!(self, _encoding) {
return Err(MultibaseError::UnknownBase);
};
Ok(data)
}
}
#[deprecated]
pub fn decode_multibase_base58btc(value: &str)
-> Result<Vec<u8>, MultibaseError>
{
Multibase::Base58Btc.decode_exact(value)
}
#[deprecated]
pub fn encode_multibase_base58btc(value: &[u8]) -> String {
Multibase::Base58Btc.encode(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multibase_test_vectors() {
let result = Multibase::Base58Btc.encode("yes mani !".as_bytes());
assert_eq!(result, "z7paNL19xttacUY");
let value = Multibase::Base58Btc.decode_exact("z7paNL19xttacUY").unwrap();
assert_eq!(value, "yes mani !".as_bytes());
let result = Multibase::Base58Btc.encode("\x00yes mani !".as_bytes());
assert_eq!(result, "z17paNL19xttacUY");
let result = Multibase::Base58Btc.encode("\x00\x00yes mani !".as_bytes());
assert_eq!(result, "z117paNL19xttacUY");
let result = Multibase::Base58Btc.encode("yes mani !".as_bytes());
assert_eq!(result, "z7paNL19xttacUY");
let result = Multibase::Base64UrlNoPad.encode("yes mani !".as_bytes());
assert_eq!(result, "ueWVzIG1hbmkgIQ");
}
#[test]
fn test_base58btc_encode_decode() {
let value = [1; 20];
let encoded = Multibase::Base58Btc.encode(&value);
let decoded = Multibase::Base58Btc.decode_exact(&encoded).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn test_base64_url_nopad_encode_decode() {
let value = [1; 20];
let encoded = Multibase::Base64UrlNoPad.encode(&value);
let decoded = Multibase::Base64UrlNoPad.decode_exact(&encoded).unwrap();
assert_eq!(decoded, value);
}
}