use std::num::ParseIntError;
use bech32::{Bech32, Bech32m, Hrp};
use borsh::{BorshDeserialize, BorshSerialize};
use hex::FromHexError;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum ByteParseError {
#[error("The input could not be decoded as bech32: {0}")]
InvalidBech32(#[from] bech32::DecodeError),
#[error("The input could not be decoded as base58: {0}")]
InvalidBase58(#[from] bs58::decode::Error),
#[error("The input {0} could not be decoded as a decimal array")]
InvalidDecimal(String),
#[error("The input contained elments that could not be parsed as integers: {0}")]
InvalidNumber(#[from] ParseIntError),
#[error("The input could not be decoded as a hex string: {0}")]
InvalidHex(#[from] FromHexError),
#[error("Invalid bech32 prefix. Expected {expected}, input contained prefix {actual}")]
InvalidBech32Prefix { expected: String, actual: String },
#[error("Invalid length: expected {expected} {encoding}-encoded bytes, but the input - once decoded - contained {actual} bytes")]
InvalidLength {
expected: usize,
encoding: String,
actual: usize,
},
}
#[derive(Debug, Error, Clone)]
pub enum ByteFormatError {
#[error("Core error: {0}")]
Core(#[from] core::fmt::Error),
#[error("The input could not be displayed as bech32: {0}")]
InvalidBech32(#[from] bech32::EncodeError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, BorshDeserialize, BorshSerialize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ByteDisplay {
#[default]
Hex,
Decimal,
Bech32 {
#[cfg_attr(feature = "serde", serde(with = "hrp_serde"))]
#[borsh(
serialize_with = "hrp_borsh::borsh_serialize",
deserialize_with = "hrp_borsh::borsh_deserialize"
)]
prefix: Hrp,
},
Bech32m {
#[cfg_attr(feature = "serde", serde(with = "hrp_serde"))]
#[borsh(
serialize_with = "hrp_borsh::borsh_serialize",
deserialize_with = "hrp_borsh::borsh_deserialize"
)]
prefix: Hrp,
},
Base58,
}
mod hrp_borsh {
use bech32::Hrp;
use borsh::{BorshDeserialize, BorshSerialize};
pub fn borsh_serialize<W: borsh::io::Write>(
hrp: &Hrp,
w: &mut W,
) -> Result<(), borsh::io::Error> {
let s = hrp.as_str();
BorshSerialize::serialize(&s, w)
}
pub fn borsh_deserialize<R: borsh::io::Read>(r: &mut R) -> Result<Hrp, borsh::io::Error> {
let s: String = BorshDeserialize::deserialize_reader(r)?;
Hrp::parse(&s).map_err(borsh::io::Error::other)
}
}
#[cfg(feature = "serde")]
mod hrp_serde {
use bech32::Hrp;
use serde::de::{self, Unexpected};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(hrp: &Hrp, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = hrp.as_str();
ser.serialize_str(s)
}
pub fn deserialize<'de, D>(d: D) -> Result<Hrp, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(d)?;
Hrp::parse(s).map_err(|_| de::Error::invalid_value(Unexpected::Str(s), &"a valid HRP"))
}
}
impl ByteDisplay {
pub fn format(
&self,
input: &[u8],
f: &mut impl core::fmt::Write,
) -> Result<(), ByteFormatError> {
match self {
ByteDisplay::Hex => {
f.write_str("0x")?;
for byte in input {
write!(f, "{byte:02x}")?;
}
}
ByteDisplay::Decimal => {
write!(f, "{input:?}")?;
}
ByteDisplay::Bech32 { prefix } => {
bech32::encode_to_fmt::<Bech32, _>(f, *prefix, input)?
}
ByteDisplay::Bech32m { prefix } => {
bech32::encode_to_fmt::<Bech32m, _>(f, *prefix, input)?
}
ByteDisplay::Base58 => {
let out = bs58::encode(input).into_string();
f.write_str(&out)?;
}
}
Ok(())
}
pub fn parse(&self, input: &str) -> Result<Vec<u8>, ByteParseError> {
match self {
ByteDisplay::Hex => {
let input = input.trim_start_matches("0x");
Ok(hex::decode(input)?)
}
ByteDisplay::Decimal => {
let Some(inner) = input
.strip_prefix('[')
.and_then(|input| input.strip_suffix(']'))
else {
return Err(ByteParseError::InvalidDecimal(input.to_string()));
};
let ret: Result<Vec<u8>, _> = inner
.split_terminator(',')
.map(|s| s.trim().parse())
.collect();
Ok(ret?)
}
ByteDisplay::Bech32 { prefix } | ByteDisplay::Bech32m { prefix } => {
let (parsed_prefix, bytes) = bech32::decode(input)?;
if parsed_prefix != *prefix {
return Err(ByteParseError::InvalidBech32Prefix {
expected: prefix.to_string(),
actual: parsed_prefix.to_string(),
});
}
Ok(bytes)
}
ByteDisplay::Base58 => Ok(bs58::decode(input).into_vec()?),
}
}
pub fn parse_const<const N: usize>(&self, input: &str) -> Result<[u8; N], ByteParseError> {
let parsed_bytes = self.parse(input)?;
let encoding = match self {
ByteDisplay::Hex => "hex",
ByteDisplay::Decimal => "decimal",
ByteDisplay::Bech32 { .. } => "bech32",
ByteDisplay::Bech32m { .. } => "bech32m",
ByteDisplay::Base58 => "base58",
}
.to_string();
<Vec<u8> as TryInto<[u8; N]>>::try_into(parsed_bytes).map_err(|bytes| {
ByteParseError::InvalidLength {
expected: N,
encoding,
actual: bytes.len(),
}
})
}
}
#[cfg(test)]
mod byte_display_tests {
use bech32::Hrp;
use super::{ByteDisplay, ByteParseError};
macro_rules! test_display_passes {
($display:expr, $str:literal) => {
let bytes = [12u8; 10];
let mut out = String::new();
let display = $display;
display.format(&bytes, &mut out).unwrap();
assert_eq!(out, $str);
};
}
macro_rules! test_parse_passes {
($display:expr, $str:literal) => {
let input = $str;
let bytes = [12u8; 10];
let display = $display;
assert_eq!(display.parse(input).unwrap(), bytes.to_vec());
assert_eq!(display.parse_const(input).unwrap(), bytes);
};
}
macro_rules! test_parse_rejects {
($display:expr, $str:literal, $encoding_for_err:literal) => {
let input = $str;
let display = $display;
let result = display.parse_const::<11>(input);
assert!(result.is_err());
assert_eq!(
result.err().unwrap(),
ByteParseError::InvalidLength {
expected: 11,
encoding: $encoding_for_err.to_string(),
actual: 10
}
)
};
}
#[test]
fn test_hex_display() {
test_display_passes!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c");
}
#[test]
fn test_hex_parse_passes() {
test_parse_passes!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c");
}
#[test]
fn test_hex_parse_failures() {
test_parse_rejects!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c", "hex");
}
#[test]
fn test_decimal_display() {
test_display_passes!(
ByteDisplay::Decimal,
"[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]"
);
}
#[test]
fn test_decimal_parse_passes() {
test_parse_passes!(
ByteDisplay::Decimal,
"[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]"
);
}
#[test]
fn test_decimal_parse_failures() {
test_parse_rejects!(
ByteDisplay::Decimal,
"[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]",
"decimal"
);
}
#[test]
fn test_bech32_display() {
test_display_passes!(
ByteDisplay::Bech32 {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqv80wveu"
);
}
#[test]
fn test_bech32_parse_passes() {
test_parse_passes!(
ByteDisplay::Bech32 {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqv80wveu"
);
}
#[test]
fn test_bech32_parse_failures() {
test_parse_rejects!(
ByteDisplay::Bech32 {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqv80wveu",
"bech32"
);
}
#[test]
fn test_bech32m_display() {
test_display_passes!(
ByteDisplay::Bech32m {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqvjn7qu7"
);
}
#[test]
fn test_bech32m_parse_passes() {
test_parse_passes!(
ByteDisplay::Bech32m {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqvjn7qu7"
);
}
#[test]
fn test_bech32m_parse_failures() {
test_parse_rejects!(
ByteDisplay::Bech32m {
prefix: Hrp::parse("aa").unwrap()
},
"aa1psxqcrqvpsxqcrqvjn7qu7",
"bech32m"
);
}
#[test]
fn test_base58_display() {
test_display_passes!(ByteDisplay::Base58, "gFqoeNwi4sf1M");
}
#[test]
fn test_base58_parse_passes() {
test_parse_passes!(ByteDisplay::Base58, "gFqoeNwi4sf1M");
}
#[test]
fn test_base58_parse_failures() {
test_parse_rejects!(ByteDisplay::Base58, "gFqoeNwi4sf1M", "base58");
}
}