Skip to main content

defuse_crypto/
fmt.rs

1//! Formatting
2
3/// Helper trait to encode/decode bytes as `<CURVE>:<base58>`
4pub trait TypedCurve {
5    /// Used as prefix: `<CURVE>:...`
6    const CURVE_TYPE: &str;
7
8    /// Encodes bytes to string as `<CURVE>:<base58>`
9    #[inline]
10    fn to_base58(bytes: impl AsRef<[u8]>) -> String {
11        format!(
12            "{}:{}",
13            Self::CURVE_TYPE,
14            bs58::encode(bytes.as_ref()).into_string()
15        )
16    }
17
18    /// Decodes bytes from string as `<CURVE>:<base58>`
19    fn parse_base58<const N: usize>(s: impl AsRef<str>) -> Result<[u8; N], ParseCurveError> {
20        let s = s.as_ref();
21        let data = if let Some((curve, data)) = s.split_once(':') {
22            if !curve.eq_ignore_ascii_case(Self::CURVE_TYPE) {
23                return Err(ParseCurveError::WrongCurveType);
24            }
25            data
26        } else {
27            s
28        };
29        checked_base58_decode_array(data)
30    }
31}
32
33/// An error returned from [`TypedCurve::parse_base58`]
34#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
35pub enum ParseCurveError {
36    #[error("wrong curve type")]
37    WrongCurveType,
38    #[error("base58: {0}")]
39    Base58(#[from] bs58::decode::Error),
40    #[error("invalid length")]
41    InvalidLength,
42}
43
44/// Base-58 decode bytes array of a an exact size.
45/// If decoded length doesn't match `N`, an error will be returned.
46///
47/// # Examples
48///
49/// ```rust
50/// # use defuse_crypto::fmt::{checked_base58_decode_array, ParseCurveError};
51/// # use hex_literal::hex;
52/// assert_eq!(
53///     checked_base58_decode_array::<8>("he11owor1d")?,
54///     hex!("04305e2b2473f058"),
55/// );
56/// checked_base58_decode_array::<7>("he11owor1d").expect_err("buffer too small");
57/// checked_base58_decode_array::<9>("he11owor1d").expect_err("buffer too large");
58/// # Ok::<(), ParseCurveError>(())
59/// ```
60#[inline]
61pub fn checked_base58_decode_array<const N: usize>(
62    input: impl AsRef<[u8]>,
63) -> Result<[u8; N], ParseCurveError> {
64    let mut output = [0u8; N];
65    let n = bs58::decode(input.as_ref())
66        // NOTE: `.into_array_const()` doesn't return an error on insufficient
67        // input length and pads the array with zeros
68        .onto(&mut output)?;
69    if n != N {
70        return Err(ParseCurveError::InvalidLength);
71    }
72    Ok(output)
73}