use crate::error::{Error, Result};
pub struct Hex;
impl Hex {
pub fn encode(data: &[u8]) -> String {
hex::encode(data)
}
pub fn encode_upper(data: &[u8]) -> String {
hex::encode_upper(data)
}
pub fn decode(s: &str) -> Result<Vec<u8>> {
hex::decode(s).map_err(|e| Error::EncodingError(e.to_string()))
}
pub fn decode_array<const N: usize>(s: &str) -> Result<[u8; N]> {
let bytes = Self::decode(s)?;
if bytes.len() != N {
return Err(Error::EncodingError(format!(
"expected {} bytes, got {}",
N,
bytes.len()
)));
}
let mut arr = [0u8; N];
arr.copy_from_slice(&bytes);
Ok(arr)
}
pub fn is_valid(s: &str) -> bool {
s.len().is_multiple_of(2) && s.chars().all(|c| c.is_ascii_hexdigit())
}
}
pub struct Base64;
impl Base64 {
pub fn encode(data: &[u8]) -> String {
<base64ct::Base64 as base64ct::Encoding>::encode_string(data)
}
pub fn decode(s: &str) -> Result<Vec<u8>> {
<base64ct::Base64 as base64ct::Encoding>::decode_vec(s)
.map_err(|e| Error::EncodingError(e.to_string()))
}
pub fn encode_url(data: &[u8]) -> String {
<base64ct::Base64UrlUnpadded as base64ct::Encoding>::encode_string(data)
}
pub fn decode_url(s: &str) -> Result<Vec<u8>> {
<base64ct::Base64UrlUnpadded as base64ct::Encoding>::decode_vec(s)
.map_err(|e| Error::EncodingError(e.to_string()))
}
#[allow(clippy::manual_div_ceil)] pub fn encoded_len(input_len: usize) -> usize {
((input_len + 2) / 3) * 4
}
pub fn decoded_len(encoded_len: usize) -> usize {
(encoded_len / 4) * 3
}
}
pub struct Base32;
impl Base32 {
pub fn encode(data: &[u8]) -> String {
<base32ct::Base32 as base32ct::Encoding>::encode_string(data)
}
pub fn decode(s: &str) -> Result<Vec<u8>> {
<base32ct::Base32 as base32ct::Encoding>::decode_vec(s)
.map_err(|e| Error::EncodingError(e.to_string()))
}
pub fn encode_unpadded(data: &[u8]) -> String {
<base32ct::Base32Unpadded as base32ct::Encoding>::encode_string(data)
}
pub fn decode_unpadded(s: &str) -> Result<Vec<u8>> {
<base32ct::Base32Unpadded as base32ct::Encoding>::decode_vec(s)
.map_err(|e| Error::EncodingError(e.to_string()))
}
}
pub struct Base58;
impl Base58 {
pub fn encode(data: &[u8]) -> String {
bs58::encode(data).into_string()
}
pub fn decode(s: &str) -> Result<Vec<u8>> {
bs58::decode(s)
.into_vec()
.map_err(|e| Error::EncodingError(e.to_string()))
}
pub fn encode_check(data: &[u8]) -> String {
use blake3::hash;
let checksum = hash(&hash(data).as_bytes()[..]);
let mut with_checksum = data.to_vec();
with_checksum.extend_from_slice(&checksum.as_bytes()[..4]);
bs58::encode(&with_checksum).into_string()
}
pub fn decode_check(s: &str) -> Result<Vec<u8>> {
use blake3::hash;
let decoded = bs58::decode(s)
.into_vec()
.map_err(|e| Error::EncodingError(e.to_string()))?;
if decoded.len() < 4 {
return Err(Error::EncodingError("Base58Check: too short".to_string()));
}
let (data, checksum) = decoded.split_at(decoded.len() - 4);
let expected = hash(&hash(data).as_bytes()[..]);
if checksum != &expected.as_bytes()[..4] {
return Err(Error::EncodingError(
"Base58Check: invalid checksum".to_string(),
));
}
Ok(data.to_vec())
}
}
pub struct Bech32;
impl Bech32 {
pub fn encode(hrp: &str, data: &[u8]) -> Result<String> {
let hrp = bech32::Hrp::parse(hrp).map_err(|e| Error::EncodingError(e.to_string()))?;
bech32::encode::<bech32::Bech32>(hrp, data).map_err(|e| Error::EncodingError(e.to_string()))
}
pub fn decode(s: &str) -> Result<(String, Vec<u8>)> {
let (hrp, data) = bech32::decode(s).map_err(|e| Error::EncodingError(e.to_string()))?;
Ok((hrp.to_string(), data))
}
pub fn encode_m(hrp: &str, data: &[u8]) -> Result<String> {
let hrp = bech32::Hrp::parse(hrp).map_err(|e| Error::EncodingError(e.to_string()))?;
bech32::encode::<bech32::Bech32m>(hrp, data)
.map_err(|e| Error::EncodingError(e.to_string()))
}
}
pub struct Pem;
impl Pem {
pub fn encode(label: &str, data: &[u8]) -> String {
let base64 = Base64::encode(data);
let mut result = String::new();
result.push_str("-----BEGIN ");
result.push_str(label);
result.push_str("-----\n");
for chunk in base64.as_bytes().chunks(64) {
result.push_str(std::str::from_utf8(chunk).unwrap());
result.push('\n');
}
result.push_str("-----END ");
result.push_str(label);
result.push_str("-----\n");
result
}
pub fn decode(pem: &str) -> Result<(String, Vec<u8>)> {
let pem = pem.trim();
let begin_marker = "-----BEGIN ";
let begin_idx = pem
.find(begin_marker)
.ok_or_else(|| Error::ParseError("missing BEGIN marker".to_string()))?;
let after_begin = &pem[begin_idx + begin_marker.len()..];
let label_end = after_begin
.find("-----")
.ok_or_else(|| Error::ParseError("malformed BEGIN marker".to_string()))?;
let label = after_begin[..label_end].to_string();
let end_marker = format!("-----END {}-----", label);
let end_idx = pem
.find(&end_marker)
.ok_or_else(|| Error::ParseError("missing END marker".to_string()))?;
let content_start = begin_idx + begin_marker.len() + label_end + 5; let content = &pem[content_start..end_idx];
let base64: String = content.chars().filter(|c| !c.is_whitespace()).collect();
let data = Base64::decode(&base64)?;
Ok((label, data))
}
}
pub struct Multibase;
impl Multibase {
pub fn encode(data: &[u8]) -> String {
multibase::encode(multibase::Base::Base58Btc, data)
}
pub fn encode_with_base(base: char, data: &[u8]) -> Result<String> {
let base =
multibase::Base::from_code(base).map_err(|e| Error::EncodingError(e.to_string()))?;
Ok(multibase::encode(base, data))
}
pub fn decode(s: &str) -> Result<Vec<u8>> {
let (_, data) = multibase::decode(s).map_err(|e| Error::EncodingError(e.to_string()))?;
Ok(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex() {
let data = b"hello world";
let encoded = Hex::encode(data);
assert_eq!(encoded, "68656c6c6f20776f726c64");
let decoded = Hex::decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_hex_array() {
let arr: [u8; 4] = Hex::decode_array("deadbeef").unwrap();
assert_eq!(arr, [0xde, 0xad, 0xbe, 0xef]);
}
#[test]
fn test_base64() {
let data = b"hello world";
let encoded = Base64::encode(data);
let decoded = Base64::decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_base64_url() {
let data = b"\xff\xfe\xfd";
let encoded = Base64::encode_url(data);
assert!(!encoded.contains('+'));
assert!(!encoded.contains('/'));
let decoded = Base64::decode_url(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_base58() {
let data = b"hello";
let encoded = Base58::encode(data);
let decoded = Base58::decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_base58_check() {
let data = b"test data";
let encoded = Base58::encode_check(data);
let decoded = Base58::decode_check(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_pem() {
let data = b"secret key data";
let pem = Pem::encode("PRIVATE KEY", data);
assert!(pem.contains("-----BEGIN PRIVATE KEY-----"));
assert!(pem.contains("-----END PRIVATE KEY-----"));
let (label, decoded) = Pem::decode(&pem).unwrap();
assert_eq!(label, "PRIVATE KEY");
assert_eq!(decoded, data);
}
#[test]
fn test_bech32() {
let data = b"\x00\x14\x75\x1e";
let encoded = Bech32::encode("bc", data).unwrap();
let (hrp, decoded) = Bech32::decode(&encoded).unwrap();
assert_eq!(hrp, "bc");
assert_eq!(decoded, data);
}
}