Skip to main content

secure_gate/traits/decoding/
bech32.rs

1// secure-gate/src/traits/decoding/bech32.rs
2
3#[cfg(feature = "encoding-bech32")]
4use super::super::helpers::bech32::{encode_lower, Bech32Large, Fe32, Fe32IterExt, Hrp};
5#[cfg(feature = "encoding-bech32")]
6use crate::error::Bech32Error;
7
8#[cfg(feature = "encoding-bech32")]
9/// Extension trait for decoding Bech32 strings to byte data.
10///
11/// Input should be treated as untrusted; use fallible methods.
12///
13/// # Security Warning
14///
15/// Decoding input from untrusted sources should use fallible `try_` methods.
16/// Invalid input may indicate tampering or errors.
17pub trait FromBech32Str {
18    /// Fallibly decode a Bech32 string to (HRP, bytes).
19    fn try_from_bech32(&self) -> Result<(String, Vec<u8>), Bech32Error>;
20
21    /// Fallibly decode a Bech32 string, expecting the specified HRP, returning bytes.
22    fn try_from_bech32_expect_hrp(&self, expected_hrp: &str) -> Result<Vec<u8>, Bech32Error>;
23}
24
25// Blanket impl to cover any AsRef<str> (e.g., &str, String, etc.)
26#[cfg(feature = "encoding-bech32")]
27impl<T: AsRef<str> + ?Sized> FromBech32Str for T {
28    fn try_from_bech32(&self) -> Result<(String, Vec<u8>), Bech32Error> {
29        let s = self.as_ref();
30        if let Some(pos) = s.find('1') {
31            let hrp_str = &s[..pos];
32            let data_str = &s[pos + 1..];
33            let hrp = Hrp::parse(hrp_str).map_err(|_| Bech32Error::InvalidHrp)?;
34            // For Bech32Large, checksum is 6 chars
35            if data_str.len() < 6 {
36                return Err(Bech32Error::OperationFailed);
37            }
38            let data_part = &data_str[..data_str.len() - 6];
39            let mut fe32s = Vec::new();
40            for c in data_part.chars() {
41                let fe = Fe32::from_char(c).map_err(|_| Bech32Error::OperationFailed)?;
42                fe32s.push(fe);
43            }
44            let data: Vec<u8> = fe32s.iter().copied().fes_to_bytes().collect();
45            // Validate by re-encoding with Bech32Large
46            let re_encoded = encode_lower::<Bech32Large>(hrp, &data)
47                .map_err(|_| Bech32Error::OperationFailed)?;
48            if re_encoded != s {
49                return Err(Bech32Error::OperationFailed);
50            }
51            if data.is_empty() {
52                return Err(Bech32Error::OperationFailed);
53            }
54            Ok((hrp.to_string(), data))
55        } else {
56            Err(Bech32Error::OperationFailed)
57        }
58    }
59
60    fn try_from_bech32_expect_hrp(&self, expected_hrp: &str) -> Result<Vec<u8>, Bech32Error> {
61        let (hrp, data) = self.try_from_bech32()?;
62        if !hrp.to_string().eq_ignore_ascii_case(expected_hrp) {
63            return Err(Bech32Error::UnexpectedHrp {
64                expected: expected_hrp.to_string(),
65                got: hrp.to_string(),
66            });
67        }
68        Ok(data)
69    }
70}