Skip to main content

secure_gate/traits/decoding/
bech32m.rs

1//! Bech32m decoding trait.
2//!
3//! > **Import path:** `use secure_gate::FromBech32mStr;`
4//!
5//! This trait provides secure, explicit decoding of Bech32m strings (BIP-350 checksum)
6//! to byte vectors, with HRP validation as the primary path. It is designed for handling
7//! untrusted input in cryptographic contexts, such as decoding encoded addresses or keys.
8//!
9//! **Requires the `encoding-bech32m` feature** (distinct from classic Bech32).
10//!
11//! # Security Notes
12//!
13//! - **Treat all input as untrusted**: validate Bech32m strings upstream before wrapping
14//!   in secrets. HRP validation prevents cross-protocol confusion attacks.
15//! - **HRP validation**: use [`try_from_bech32m`](FromBech32mStr::try_from_bech32m) as the
16//!   default; use [`try_from_bech32m_unchecked`](FromBech32mStr::try_from_bech32m_unchecked)
17//!   only when you intentionally need the decoded HRP. Test empty and invalid HRP inputs
18//!   in security-critical code.
19//! - **Heap allocation**: Returns `Vec<u8>` — wrap in [`Fixed`](crate::Fixed) or
20//!   [`Dynamic`](crate::Dynamic) to store as a secret.
21//! - **BIP-350 checksum**: Enhanced error detection over BIP-173 Bech32.
22//! - **Standard 90-byte payload limit (by design)**: decodes only spec-compliant
23//!   Bech32m strings intended for Bitcoin address formats. Strings produced by
24//!   the extended [`ToBech32`](crate::ToBech32) / `Bech32Large` variant are a
25//!   distinct format — decode those with [`FromBech32Str`](crate::FromBech32Str).
26//!
27//! # Example
28//!
29//! ```rust
30//! use secure_gate::FromBech32mStr;
31//! # #[cfg(feature = "encoding-bech32m")]
32//! # {
33//!
34//! // BIP-350 minimal valid Bech32m test vector
35//! let bech32m = "A1LQFN3A";
36//!
37//! let data = bech32m.try_from_bech32m("A").expect("HRP matches");
38//! assert!(data.is_empty());
39//!
40//! let (hrp, data) = bech32m.try_from_bech32m_unchecked().expect("valid bech32m");
41//! assert_eq!(hrp.to_ascii_lowercase(), "a");
42//! assert!(data.is_empty());
43//!
44//! // Error on invalid input
45//! assert!("not-bech32m".try_from_bech32m("a").is_err());
46//! # }
47//! ```
48#[cfg(feature = "encoding-bech32m")]
49use bech32::{Bech32m, primitives::decode::CheckedHrpstring};
50#[cfg(feature = "encoding-bech32m")]
51use crate::error::Bech32Error;
52
53/// Extension trait for decoding Bech32m (BIP-350) strings into byte vectors.
54///
55/// *Requires feature `encoding-bech32m`.*
56///
57/// Blanket-implemented for all `AsRef<str>` types. Treat all input as untrusted;
58/// HRP validation prevents injection attacks and cross-protocol confusion.
59///
60/// **Design note — standard BIP-350 compliance**: decodes only standard-length
61/// Bech32m strings (Bitcoin Taproot/SegWit v1+ compatible). The `Bech32Large`
62/// variant used by [`ToBech32`](crate::ToBech32) is a distinct non-standard format
63/// for large payloads; decode those with [`FromBech32Str`](crate::FromBech32Str).
64#[cfg(feature = "encoding-bech32m")]
65pub trait FromBech32mStr {
66    /// Decodes a Bech32m (BIP-350) string, validating that the HRP matches `expected_hrp`.
67    ///
68    /// The HRP comparison is case-insensitive. Returns only the data bytes — the HRP
69    /// is validated and discarded.
70    ///
71    /// Validates the BIP-350 checksum.
72    ///
73    /// # Errors
74    ///
75    /// - [`Bech32Error::OperationFailed`] — invalid checksum or malformed string.
76    /// - [`Bech32Error::UnexpectedHrp`] — decoded HRP does not match `expected_hrp`.
77    ///
78    /// # Examples
79    ///
80    /// ```rust
81    /// use secure_gate::FromBech32mStr;
82    ///
83    /// // BIP-350 minimal valid test vector
84    /// let data = "A1LQFN3A".try_from_bech32m("A")?;
85    /// assert!(data.is_empty());
86    ///
87    /// // HRP mismatch returns an error
88    /// assert!("A1LQFN3A".try_from_bech32m("bc").is_err());
89    /// # Ok::<(), secure_gate::Bech32Error>(())
90    /// ```
91    fn try_from_bech32m(&self, expected_hrp: &str) -> Result<Vec<u8>, Bech32Error>;
92
93    /// Decodes a Bech32m (BIP-350) string into `(HRP, data_bytes)` without validating the HRP.
94    ///
95    /// Validates the BIP-350 checksum.
96    ///
97    /// # Errors
98    ///
99    /// - [`Bech32Error::OperationFailed`] — invalid checksum or malformed string.
100    /// - [`Bech32Error::ConversionFailed`] — bit-conversion failure.
101    ///
102    /// # Examples
103    ///
104    /// ```rust
105    /// use secure_gate::FromBech32mStr;
106    ///
107    /// // BIP-350 minimal valid test vector
108    /// let (hrp, data) = "A1LQFN3A".try_from_bech32m_unchecked()?;
109    /// assert_eq!(hrp.to_ascii_lowercase(), "a");
110    /// assert!(data.is_empty());
111    /// # Ok::<(), secure_gate::Bech32Error>(())
112    /// ```
113    fn try_from_bech32m_unchecked(&self) -> Result<(String, Vec<u8>), Bech32Error>;
114}
115
116// Blanket impl to cover any AsRef<str> (e.g., &str, String, etc.)
117#[cfg(feature = "encoding-bech32m")]
118impl<T: AsRef<str> + ?Sized> FromBech32mStr for T {
119    fn try_from_bech32m_unchecked(&self) -> Result<(String, Vec<u8>), Bech32Error> {
120        let s = self.as_ref();
121        // Use CheckedHrpstring to validate Bech32m checksum (no-alloc)
122        let checked =
123            CheckedHrpstring::new::<Bech32m>(s).map_err(|_| Bech32Error::OperationFailed)?;
124
125        // Get HRP (lowercase)
126        let hrp = checked.hrp().to_string();
127
128        // Collect data as 8-bit bytes (handles empty)
129        let data: Vec<u8> = checked.byte_iter().collect();
130
131        Ok((hrp, data))
132    }
133
134    fn try_from_bech32m(&self, expected_hrp: &str) -> Result<Vec<u8>, Bech32Error> {
135        let (got_hrp, data) = self.try_from_bech32m_unchecked()?;
136        if !got_hrp.eq_ignore_ascii_case(expected_hrp) {
137            #[cfg(debug_assertions)]
138            return Err(Bech32Error::UnexpectedHrp {
139                expected: expected_hrp.to_string(),
140                got: got_hrp,
141            });
142            #[cfg(not(debug_assertions))]
143            return Err(Bech32Error::UnexpectedHrp);
144        }
145        Ok(data)
146    }
147}