#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
use super::Case;
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
use bech32::Hrp;
#[cfg(feature = "encoding-bech32")]
use bech32::primitives::checksum::Checksum;
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
use bech32::primitives::iter::{ByteIterExt, Fe32IterExt};
#[cfg(feature = "encoding-bech32")]
use super::bech32::GENERATOR_SH;
#[cfg(feature = "encoding-bech32")]
pub use super::bech32::{BECH32_CODE_LENGTH, bech32_code_length};
#[cfg(feature = "encoding-bech32")]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Bech32mSized<const CODE_LENGTH: usize> {}
#[cfg(feature = "encoding-bech32")]
impl<const N: usize> Checksum for Bech32mSized<N> {
type MidstateRepr = u32;
const CODE_LENGTH: usize = N;
const CHECKSUM_LENGTH: usize = 6;
const GENERATOR_SH: [u32; 5] = GENERATOR_SH;
const TARGET_RESIDUE: u32 = 0x2bc8_30a3;
}
#[cfg(feature = "encoding-bech32")]
pub type Bech32mStandard = Bech32mSized<BECH32_CODE_LENGTH>;
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
use crate::error::Bech32Error;
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
pub trait ToBech32m {
fn try_to_bech32m(&self, hrp: &str, case: Case) -> Result<crate::EncodedSecret, Bech32Error>;
fn try_to_bech32m_sized<const N: usize>(
&self,
hrp: &str,
case: Case,
) -> Result<crate::EncodedSecret, Bech32Error>;
}
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
impl<T: AsRef<[u8]> + super::EncodableBytes + ?Sized> ToBech32m for T {
#[inline(always)]
fn try_to_bech32m(&self, hrp: &str, case: Case) -> Result<crate::EncodedSecret, Bech32Error> {
self.try_to_bech32m_sized::<BECH32_CODE_LENGTH>(hrp, case)
}
#[inline(always)]
fn try_to_bech32m_sized<const N: usize>(
&self,
hrp: &str,
case: Case,
) -> Result<crate::EncodedSecret, Bech32Error> {
let hrp_parsed = Hrp::parse(hrp).map_err(|_| Bech32Error::InvalidHrp)?;
let data = self.as_ref();
let len = bech32_code_length(hrp.len(), data.len());
if len > N {
return Err(Bech32Error::OperationFailed);
}
let mut out = alloc::string::String::with_capacity(len);
let chain = data
.iter()
.copied()
.bytes_to_fes()
.with_checksum::<Bech32mSized<N>>(&hrp_parsed)
.chars();
for c in chain {
out.push(c);
}
debug_assert_eq!(
out.len(),
len,
"bech32_code_length disagreed with the encoder"
);
if matches!(case, Case::Upper) {
out.make_ascii_uppercase();
}
Ok(crate::EncodedSecret::new(out))
}
}
#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn direct_chain_matches_upstream_encode_lower() {
use bech32::{Hrp, encode_lower};
for (hrp, len) in [
("a", 0usize),
("kem", 32),
("kem", 633),
("kem", 1568),
("x", 4096),
] {
let data: alloc::vec::Vec<u8> = (0..len).map(|i| (i * 131 + 7) as u8).collect();
let ours = data
.try_to_bech32m_sized::<65535>(hrp, Case::Lower)
.expect("ours");
let theirs = encode_lower::<Bech32mSized<65535>>(Hrp::parse(hrp).unwrap(), &data)
.expect("upstream");
assert_eq!(&*ours, &*theirs, "hrp={hrp} len={len}");
}
}
#[test]
fn default_code_length_rejects_oversized_payload() {
let large_data = vec![0u8; 800];
assert_eq!(
large_data.try_to_bech32m("test", Case::Lower).unwrap_err(),
crate::error::Bech32Error::OperationFailed
);
}
#[test]
fn sized_accepts_what_the_default_rejects() {
let large_data = vec![0u8; 800];
assert!(large_data.try_to_bech32m("test", Case::Lower).is_err());
let encoded = large_data
.try_to_bech32m_sized::<2048>("test", Case::Lower)
.expect("2048 is long enough");
assert!(encoded.starts_with("test1"));
}
}