use crate::error::Error;
use crate::error::Result;
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub enum Base {
Base2,
Base8,
Base10,
Base16Lower,
Base16Upper,
Base32Lower,
Base32Upper,
Base32PadLower,
Base32PadUpper,
Base32HexLower,
Base32HexUpper,
Base32HexPadLower,
Base32HexPadUpper,
Base32Z,
Base36Lower,
Base36Upper,
Base58Flickr,
Base58Btc,
Base64,
Base64Pad,
Base64Url,
Base64UrlPad,
}
impl From<Base> for multibase::Base {
fn from(base: Base) -> Self {
match base {
Base::Base2 => multibase::Base::Base2,
Base::Base8 => multibase::Base::Base8,
Base::Base10 => multibase::Base::Base10,
Base::Base16Lower => multibase::Base::Base16Lower,
Base::Base16Upper => multibase::Base::Base16Upper,
Base::Base32Lower => multibase::Base::Base32Lower,
Base::Base32Upper => multibase::Base::Base32Upper,
Base::Base32PadLower => multibase::Base::Base32PadLower,
Base::Base32PadUpper => multibase::Base::Base32PadUpper,
Base::Base32HexLower => multibase::Base::Base32HexLower,
Base::Base32HexUpper => multibase::Base::Base32HexUpper,
Base::Base32HexPadLower => multibase::Base::Base32HexPadLower,
Base::Base32HexPadUpper => multibase::Base::Base32HexPadUpper,
Base::Base32Z => multibase::Base::Base32Z,
Base::Base36Lower => multibase::Base::Base36Lower,
Base::Base36Upper => multibase::Base::Base36Upper,
Base::Base58Flickr => multibase::Base::Base58Flickr,
Base::Base58Btc => multibase::Base::Base58Btc,
Base::Base64 => multibase::Base::Base64,
Base::Base64Pad => multibase::Base::Base64Pad,
Base::Base64Url => multibase::Base::Base64Url,
Base::Base64UrlPad => multibase::Base::Base64UrlPad,
}
}
}
pub struct BaseEncoding;
impl BaseEncoding {
pub fn encode<T>(data: &T, base: Base) -> String
where
T: AsRef<[u8]> + ?Sized,
{
multibase::Base::from(base).encode(data)
}
pub fn decode<T>(data: &T, base: Base) -> Result<Vec<u8>>
where
T: AsRef<str> + ?Sized,
{
multibase::Base::from(base)
.decode(data)
.map_err(|err| Error::DecodeBase(base, err))
}
pub fn encode_base58<T>(data: &T) -> String
where
T: AsRef<[u8]> + ?Sized,
{
Self::encode(data, Base::Base58Btc)
}
pub fn decode_base58<T>(data: &T) -> Result<Vec<u8>>
where
T: AsRef<str> + ?Sized,
{
Self::decode(data, Base::Base58Btc)
}
pub fn encode_multibase<T>(data: &T, base: Option<Base>) -> String
where
T: AsRef<[u8]> + ?Sized,
{
multibase::encode(multibase::Base::from(base.unwrap_or(Base::Base58Btc)), data)
}
pub fn decode_multibase<T>(data: &T) -> Result<Vec<u8>>
where
T: AsRef<str> + ?Sized,
{
if data.as_ref().is_empty() {
return Ok(Vec::new());
}
multibase::decode(data)
.map(|(_base, output)| output)
.map_err(Error::DecodeMultibase)
}
}
#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;
use super::*;
#[test]
fn test_decode_base58_empty() {
assert_eq!(BaseEncoding::decode_base58("").unwrap(), Vec::<u8>::new());
}
#[quickcheck]
fn test_base58_random(data: Vec<u8>) {
assert_eq!(
BaseEncoding::decode_base58(&BaseEncoding::encode_base58(&data)).unwrap(),
data
);
}
#[quickcheck]
fn test_base64_random(data: Vec<u8>) {
assert_eq!(
BaseEncoding::decode(&BaseEncoding::encode(&data, Base::Base64Url), Base::Base64Url).unwrap(),
data
);
}
#[test]
fn test_b58() {
let test_vectors: [(&[u8], &str); 3] = [
(b"Hello World!", "2NEpo7TZRRrLZSi2U"),
(
b"The quick brown fox jumps over the lazy dog.",
"USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z",
),
(&[0, 0, 0, 40, 127, 180, 205], "111233QC4"),
];
for (test_decoded, test_encoded) in test_vectors {
let encoded: String = BaseEncoding::encode_base58(test_decoded);
assert_eq!(encoded, test_encoded, "encode failed on {test_decoded:?}");
let decoded: Vec<u8> = BaseEncoding::decode_base58(test_encoded).unwrap();
assert_eq!(decoded, test_decoded, "decode failed on {test_encoded}");
}
}
#[test]
fn test_decode_multibase_empty() {
assert_eq!(BaseEncoding::decode_multibase("").unwrap(), Vec::<u8>::new());
}
#[quickcheck]
fn test_multibase_random(data: Vec<u8>) {
assert_eq!(
BaseEncoding::decode_multibase(&BaseEncoding::encode_multibase(&data, None)).unwrap(),
data
);
}
#[quickcheck]
fn test_multibase_bases_random(data: Vec<u8>) {
let bases = [
Base::Base2,
Base::Base8,
Base::Base10,
Base::Base16Lower,
Base::Base16Upper,
Base::Base32Lower,
Base::Base32Upper,
Base::Base32PadLower,
Base::Base32PadUpper,
Base::Base32HexLower,
Base::Base32HexUpper,
Base::Base32HexPadLower,
Base::Base32HexPadUpper,
Base::Base32Z,
Base::Base36Lower,
Base::Base36Upper,
Base::Base58Flickr,
Base::Base58Btc,
Base::Base64,
Base::Base64Pad,
Base::Base64Url,
Base::Base64UrlPad,
];
for base in bases {
assert_eq!(
BaseEncoding::decode_multibase(&BaseEncoding::encode_multibase(&data, Some(base))).unwrap(),
data
);
}
}
#[test]
fn test_multibase() {
let data: &str = r"Multibase is awesome! \o/";
for (base, expected) in [
(Base::Base16Upper, "F4D756C74696261736520697320617765736F6D6521205C6F2F"),
(Base::Base32Upper, "BJV2WY5DJMJQXGZJANFZSAYLXMVZW63LFEEQFY3ZP"),
(Base::Base58Btc, "zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt"),
(Base::Base64Pad, "MTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw=="),
] {
let encoded: String = BaseEncoding::encode_multibase(data, Some(base));
assert_eq!(encoded, expected);
}
let expected: Vec<u8> = data.as_bytes().to_vec();
for encoded in [
"F4D756C74696261736520697320617765736F6D6521205C6F2F",
"BJV2WY5DJMJQXGZJANFZSAYLXMVZW63LFEEQFY3ZP",
"zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt",
"MTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw==",
] {
let decoded: Vec<u8> = BaseEncoding::decode_multibase(encoded).unwrap();
assert_eq!(decoded, expected, "failed on {encoded}");
}
}
}