secure_gate/encoding/
bech32.rs1#![cfg_attr(not(feature = "zeroize"), forbid(unsafe_code))]
23
24use alloc::string::String;
25
26use bech32::primitives::decode::UncheckedHrpstring;
27use bech32::{decode, primitives::hrp::Hrp, Bech32, Bech32m};
28
29use crate::traits::expose_secret::ExposeSecret;
30
31fn zeroize_input(s: &mut String) {
32 #[cfg(feature = "zeroize")]
33 {
34 zeroize::Zeroize::zeroize(s);
35 }
36 #[cfg(not(feature = "zeroize"))]
37 {
38 let _ = s; }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum EncodingVariant {
48 Bech32,
50 Bech32m,
52}
53
54pub struct Bech32String {
56 pub(crate) inner: crate::Dynamic<String>,
57 pub(crate) variant: EncodingVariant,
58}
59
60impl Bech32String {
61 pub fn new(mut s: String) -> Result<Self, &'static str> {
71 let unchecked = UncheckedHrpstring::new(&s).map_err(|_| "invalid bech32 string")?;
72 let variant = if unchecked.validate_checksum::<Bech32>().is_ok() {
73 EncodingVariant::Bech32
74 } else if unchecked.validate_checksum::<Bech32m>().is_ok() {
75 EncodingVariant::Bech32m
76 } else {
77 zeroize_input(&mut s);
78 return Err("invalid bech32 string");
79 };
80
81 s.make_ascii_lowercase();
83
84 Ok(Self {
85 inner: crate::Dynamic::new(s),
86 variant,
87 })
88 }
89
90 #[inline(always)]
92 pub fn is_bech32(&self) -> bool {
93 self.variant == EncodingVariant::Bech32
94 }
95
96 #[inline(always)]
98 pub fn is_bech32m(&self) -> bool {
99 self.variant == EncodingVariant::Bech32m
100 }
101
102 pub fn hrp(&self) -> Hrp {
104 let (hrp, _) =
105 decode(self.inner.expose_secret().as_str()).expect("Bech32String is always valid");
106 hrp
107 }
108
109 pub fn byte_len(&self) -> usize {
111 let s = self.inner.expose_secret().as_str();
112 let sep_pos = s.rfind('1').expect("valid bech32 has '1' separator");
113 let data_part_len = s.len() - sep_pos - 1;
114 let data_chars = data_part_len - 6; (data_chars * 5) / 8
116 }
117
118 pub fn decode_to_bytes(&self) -> Vec<u8> {
120 let (_, data) = decode(self.inner.expose_secret().as_str())
121 .expect("Bech32String invariant: always valid");
122 data
123 }
124
125 pub fn variant(&self) -> EncodingVariant {
127 self.variant
128 }
129
130 pub fn decode_into_bytes(self) -> Vec<u8> {
132 let (_, data) =
133 decode(self.inner.expose_secret().as_str()).expect("Bech32String is always valid");
134 data
135 }
136}
137
138#[cfg(feature = "ct-eq")]
140impl PartialEq for Bech32String {
141 fn eq(&self, other: &Self) -> bool {
142 use crate::traits::ConstantTimeEq;
143 self.inner
144 .expose_secret()
145 .as_bytes()
146 .ct_eq(other.inner.expose_secret().as_bytes())
147 }
148}
149
150#[cfg(not(feature = "ct-eq"))]
152impl PartialEq for Bech32String {
153 fn eq(&self, other: &Self) -> bool {
154 self.inner.expose_secret() == other.inner.expose_secret()
155 }
156}
157
158impl Eq for Bech32String {}
160
161impl core::fmt::Debug for Bech32String {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 f.write_str("[REDACTED]")
165 }
166}