1use std::num::ParseIntError;
2
3use bech32::{Bech32, Bech32m, Hrp};
4use borsh::{BorshDeserialize, BorshSerialize};
5use hex::FromHexError;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10#[derive(Debug, Error, PartialEq)]
11pub enum ByteParseError {
12 #[error("The input could not be decoded as bech32: {0}")]
13 InvalidBech32(#[from] bech32::DecodeError),
14 #[error("The input could not be decoded as base58: {0}")]
15 InvalidBase58(#[from] bs58::decode::Error),
16 #[error("The input {0} could not be decoded as a decimal array")]
17 InvalidDecimal(String),
18 #[error("The input contained elments that could not be parsed as integers: {0}")]
19 InvalidNumber(#[from] ParseIntError),
20 #[error("The input could not be decoded as a hex string: {0}")]
21 InvalidHex(#[from] FromHexError),
22 #[error("Invalid bech32 prefix. Expected {expected}, input contained prefix {actual}")]
23 InvalidBech32Prefix { expected: String, actual: String },
24 #[error("Invalid length: expected {expected} {encoding}-encoded bytes, but the input - once decoded - contained {actual} bytes")]
25 InvalidLength {
26 expected: usize,
27 encoding: String,
28 actual: usize,
29 },
30}
31
32#[derive(Debug, Error, Clone)]
33pub enum ByteFormatError {
34 #[error("Core error: {0}")]
35 Core(#[from] core::fmt::Error),
36 #[error("The input could not be displayed as bech32: {0}")]
37 InvalidBech32(#[from] bech32::EncodeError),
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, BorshDeserialize, BorshSerialize)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42pub enum ByteDisplay {
43 #[default]
44 Hex,
45 Decimal,
46 Bech32 {
47 #[cfg_attr(feature = "serde", serde(with = "hrp_serde"))]
48 #[borsh(
49 serialize_with = "hrp_borsh::borsh_serialize",
50 deserialize_with = "hrp_borsh::borsh_deserialize"
51 )]
52 prefix: Hrp,
53 },
54 Bech32m {
55 #[cfg_attr(feature = "serde", serde(with = "hrp_serde"))]
56 #[borsh(
57 serialize_with = "hrp_borsh::borsh_serialize",
58 deserialize_with = "hrp_borsh::borsh_deserialize"
59 )]
60 prefix: Hrp,
61 },
62 Base58,
63}
64
65mod hrp_borsh {
66 use bech32::Hrp;
67 use borsh::{BorshDeserialize, BorshSerialize};
68
69 pub fn borsh_serialize<W: borsh::io::Write>(
70 hrp: &Hrp,
71 w: &mut W,
72 ) -> Result<(), borsh::io::Error> {
73 let s = hrp.as_str();
74 BorshSerialize::serialize(&s, w)
75 }
76
77 pub fn borsh_deserialize<R: borsh::io::Read>(r: &mut R) -> Result<Hrp, borsh::io::Error> {
78 let s: String = BorshDeserialize::deserialize_reader(r)?;
79 Hrp::parse(&s).map_err(borsh::io::Error::other)
80 }
81}
82
83#[cfg(feature = "serde")]
84mod hrp_serde {
85 use bech32::Hrp;
86 use serde::de::{self, Unexpected};
87 use serde::{Deserialize, Deserializer, Serializer};
88
89 pub fn serialize<S>(hrp: &Hrp, ser: S) -> Result<S::Ok, S::Error>
90 where
91 S: Serializer,
92 {
93 let s = hrp.as_str();
94 ser.serialize_str(s)
95 }
96
97 pub fn deserialize<'de, D>(d: D) -> Result<Hrp, D::Error>
98 where
99 D: Deserializer<'de>,
100 {
101 let s = <&str>::deserialize(d)?;
102 Hrp::parse(s).map_err(|_| de::Error::invalid_value(Unexpected::Str(s), &"a valid HRP"))
103 }
104}
105impl ByteDisplay {
106 pub fn format(
107 &self,
108 input: &[u8],
109 f: &mut impl core::fmt::Write,
110 ) -> Result<(), ByteFormatError> {
111 match self {
112 ByteDisplay::Hex => {
113 f.write_str("0x")?;
114 for byte in input {
115 write!(f, "{byte:02x}")?;
116 }
117 }
118 ByteDisplay::Decimal => {
119 write!(f, "{input:?}")?;
120 }
121 ByteDisplay::Bech32 { prefix } => {
122 bech32::encode_to_fmt::<Bech32, _>(f, *prefix, input)?
123 }
124 ByteDisplay::Bech32m { prefix } => {
125 bech32::encode_to_fmt::<Bech32m, _>(f, *prefix, input)?
126 }
127 ByteDisplay::Base58 => {
128 let out = bs58::encode(input).into_string();
129 f.write_str(&out)?;
130 }
131 }
132 Ok(())
133 }
134
135 pub fn parse(&self, input: &str) -> Result<Vec<u8>, ByteParseError> {
136 match self {
137 ByteDisplay::Hex => {
138 let input = input.trim_start_matches("0x");
139 Ok(hex::decode(input)?)
140 }
141 ByteDisplay::Decimal => {
142 let Some(inner) = input
143 .strip_prefix('[')
144 .and_then(|input| input.strip_suffix(']'))
145 else {
146 return Err(ByteParseError::InvalidDecimal(input.to_string()));
147 };
148 let ret: Result<Vec<u8>, _> = inner
149 .split_terminator(',')
150 .map(|s| s.trim().parse())
151 .collect();
152 Ok(ret?)
153 }
154 ByteDisplay::Bech32 { prefix } | ByteDisplay::Bech32m { prefix } => {
155 let (parsed_prefix, bytes) = bech32::decode(input)?;
156 if parsed_prefix != *prefix {
157 return Err(ByteParseError::InvalidBech32Prefix {
158 expected: prefix.to_string(),
159 actual: parsed_prefix.to_string(),
160 });
161 }
162 Ok(bytes)
163 }
164 ByteDisplay::Base58 => Ok(bs58::decode(input).into_vec()?),
165 }
166 }
167
168 pub fn parse_const<const N: usize>(&self, input: &str) -> Result<[u8; N], ByteParseError> {
169 let parsed_bytes = self.parse(input)?;
170 let encoding = match self {
171 ByteDisplay::Hex => "hex",
172 ByteDisplay::Decimal => "decimal",
173 ByteDisplay::Bech32 { .. } => "bech32",
174 ByteDisplay::Bech32m { .. } => "bech32m",
175 ByteDisplay::Base58 => "base58",
176 }
177 .to_string();
178 <Vec<u8> as TryInto<[u8; N]>>::try_into(parsed_bytes).map_err(|bytes| {
179 ByteParseError::InvalidLength {
180 expected: N,
181 encoding,
182 actual: bytes.len(),
183 }
184 })
185 }
186}
187#[cfg(test)]
188mod byte_display_tests {
189 use bech32::Hrp;
190
191 use super::{ByteDisplay, ByteParseError};
192
193 macro_rules! test_display_passes {
197 ($display:expr, $str:literal) => {
198 let bytes = [12u8; 10];
199 let mut out = String::new();
200 let display = $display;
201
202 display.format(&bytes, &mut out).unwrap();
203 assert_eq!(out, $str);
204 };
205 }
206
207 macro_rules! test_parse_passes {
208 ($display:expr, $str:literal) => {
209 let input = $str;
210 let bytes = [12u8; 10];
211 let display = $display;
212
213 assert_eq!(display.parse(input).unwrap(), bytes.to_vec());
215 assert_eq!(display.parse_const(input).unwrap(), bytes);
217 };
218 }
219
220 macro_rules! test_parse_rejects {
221 ($display:expr, $str:literal, $encoding_for_err:literal) => {
222 let input = $str;
223 let display = $display;
224
225 let result = display.parse_const::<11>(input);
226 assert!(result.is_err());
227 assert_eq!(
228 result.err().unwrap(),
229 ByteParseError::InvalidLength {
230 expected: 11,
231 encoding: $encoding_for_err.to_string(),
232 actual: 10
233 }
234 )
235 };
236 }
237
238 #[test]
239 fn test_hex_display() {
240 test_display_passes!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c");
241 }
242
243 #[test]
244 fn test_hex_parse_passes() {
245 test_parse_passes!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c");
246 }
247
248 #[test]
249 fn test_hex_parse_failures() {
250 test_parse_rejects!(ByteDisplay::Hex, "0x0c0c0c0c0c0c0c0c0c0c", "hex");
251 }
252
253 #[test]
254 fn test_decimal_display() {
255 test_display_passes!(
256 ByteDisplay::Decimal,
257 "[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]"
258 );
259 }
260
261 #[test]
262 fn test_decimal_parse_passes() {
263 test_parse_passes!(
264 ByteDisplay::Decimal,
265 "[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]"
266 );
267 }
268
269 #[test]
270 fn test_decimal_parse_failures() {
271 test_parse_rejects!(
272 ByteDisplay::Decimal,
273 "[12, 12, 12, 12, 12, 12, 12, 12, 12, 12]",
274 "decimal"
275 );
276 }
277
278 #[test]
279 fn test_bech32_display() {
280 test_display_passes!(
281 ByteDisplay::Bech32 {
282 prefix: Hrp::parse("aa").unwrap()
283 },
284 "aa1psxqcrqvpsxqcrqv80wveu"
285 );
286 }
287
288 #[test]
289 fn test_bech32_parse_passes() {
290 test_parse_passes!(
291 ByteDisplay::Bech32 {
292 prefix: Hrp::parse("aa").unwrap()
293 },
294 "aa1psxqcrqvpsxqcrqv80wveu"
295 );
296 }
297
298 #[test]
299 fn test_bech32_parse_failures() {
300 test_parse_rejects!(
301 ByteDisplay::Bech32 {
302 prefix: Hrp::parse("aa").unwrap()
303 },
304 "aa1psxqcrqvpsxqcrqv80wveu",
305 "bech32"
306 );
307 }
308
309 #[test]
310 fn test_bech32m_display() {
311 test_display_passes!(
312 ByteDisplay::Bech32m {
313 prefix: Hrp::parse("aa").unwrap()
314 },
315 "aa1psxqcrqvpsxqcrqvjn7qu7"
316 );
317 }
318
319 #[test]
320 fn test_bech32m_parse_passes() {
321 test_parse_passes!(
322 ByteDisplay::Bech32m {
323 prefix: Hrp::parse("aa").unwrap()
324 },
325 "aa1psxqcrqvpsxqcrqvjn7qu7"
326 );
327 }
328
329 #[test]
330 fn test_bech32m_parse_failures() {
331 test_parse_rejects!(
332 ByteDisplay::Bech32m {
333 prefix: Hrp::parse("aa").unwrap()
334 },
335 "aa1psxqcrqvpsxqcrqvjn7qu7",
336 "bech32m"
337 );
338 }
339
340 #[test]
341 fn test_base58_display() {
342 test_display_passes!(ByteDisplay::Base58, "gFqoeNwi4sf1M");
343 }
344
345 #[test]
346 fn test_base58_parse_passes() {
347 test_parse_passes!(ByteDisplay::Base58, "gFqoeNwi4sf1M");
348 }
349
350 #[test]
351 fn test_base58_parse_failures() {
352 test_parse_rejects!(ByteDisplay::Base58, "gFqoeNwi4sf1M", "base58");
353 }
354}