use core::{fmt, iter, slice, str};
use crate::error::write_err;
use bech32::primitives::checksum::{self, Checksum};
use bech32::primitives::gf32::Fe32;
use bech32::primitives::hrp::{self, Hrp};
use bech32::primitives::iter::{Fe32IterExt, FesToBytes};
use bech32::primitives::segwit::{WitnessLengthError, VERSION_0};
use super::{Blech32, Blech32m};
const SEP: char = '1';
#[derive(Debug)]
pub struct UncheckedHrpstring<'s> {
hrp: Hrp,
data: &'s [u8],
}
impl<'s> UncheckedHrpstring<'s> {
#[inline]
pub fn new(s: &'s str) -> Result<Self, UncheckedHrpstringError> {
let sep_pos = check_characters(s)?;
let (hrp, data) = s.split_at(sep_pos);
let ret = UncheckedHrpstring {
hrp: Hrp::parse(hrp)?,
data: &data.as_bytes()[1..], };
Ok(ret)
}
#[inline]
pub fn hrp(&self) -> Hrp { self.hrp }
#[inline]
pub fn validate_and_remove_checksum<Ck: Checksum>(
self,
) -> Result<CheckedHrpstring<'s>, ChecksumError> {
self.validate_checksum::<Ck>()?;
Ok(self.remove_checksum::<Ck>())
}
#[inline]
pub fn has_valid_checksum<Ck: Checksum>(&self) -> bool {
self.validate_checksum::<Ck>().is_ok()
}
#[inline]
pub fn validate_checksum<Ck: Checksum>(&self) -> Result<(), ChecksumError> {
use ChecksumError as E;
if Ck::CHECKSUM_LENGTH == 0 {
return Ok(());
}
if self.data.len() < Ck::CHECKSUM_LENGTH {
return Err(E::InvalidChecksumLength);
}
let mut checksum_eng = checksum::Engine::<Ck>::new();
checksum_eng.input_hrp(self.hrp());
for fe in self.data.iter().map(|&b| Fe32::from_char(b.into()).unwrap()) {
checksum_eng.input_fe(fe);
}
if checksum_eng.residue() != &Ck::TARGET_RESIDUE {
return Err(E::InvalidChecksum);
}
Ok(())
}
#[inline]
pub fn remove_checksum<Ck: Checksum>(self) -> CheckedHrpstring<'s> {
let data_len = self.data.len() - Ck::CHECKSUM_LENGTH;
CheckedHrpstring { hrp: self.hrp(), data: &self.data[..data_len] }
}
}
#[derive(Debug)]
pub struct CheckedHrpstring<'s> {
hrp: Hrp,
data: &'s [u8],
}
impl<'s> CheckedHrpstring<'s> {
#[inline]
pub fn new<Ck: Checksum>(s: &'s str) -> Result<Self, CheckedHrpstringError> {
let unchecked = UncheckedHrpstring::new(s)?;
let checked = unchecked.validate_and_remove_checksum::<Ck>()?;
Ok(checked)
}
#[inline]
pub fn hrp(&self) -> Hrp { self.hrp }
#[inline]
pub fn byte_iter(&self) -> ByteIter<'_> {
ByteIter { iter: AsciiToFe32Iter { iter: self.data.iter().copied() }.fes_to_bytes() }
}
#[inline]
pub fn validate_segwit(mut self) -> Result<SegwitHrpstring<'s>, SegwitHrpstringError> {
if self.data.is_empty() {
return Err(SegwitHrpstringError::MissingWitnessVersion);
}
let witness_version = Fe32::from_char(self.data[0].into()).unwrap();
self.data = &self.data[1..];
self.validate_padding()?;
self.validate_witness_program_length(witness_version)?;
Ok(SegwitHrpstring { hrp: self.hrp(), witness_version, data: self.data })
}
fn validate_padding(&self) -> Result<(), PaddingError> {
if self.data.is_empty() {
return Ok(()); }
let fe_iter = AsciiToFe32Iter { iter: self.data.iter().copied() };
let padding_len = fe_iter.len() * 5 % 8;
if padding_len > 4 {
return Err(PaddingError::TooMuch)?;
}
let last_fe = fe_iter.last().expect("checked above");
let last_byte = last_fe.to_u8();
let padding_contains_non_zero_bits = match padding_len {
0 => false,
1 => last_byte & 0b0001 > 0,
2 => last_byte & 0b0011 > 0,
3 => last_byte & 0b0111 > 0,
4 => last_byte & 0b1111 > 0,
_ => unreachable!("checked above"),
};
if padding_contains_non_zero_bits {
Err(PaddingError::NonZero)
} else {
Ok(())
}
}
fn validate_witness_program_length(
&self,
witness_version: Fe32,
) -> Result<(), WitnessLengthError> {
let len = self.byte_iter().len();
if len < 2 {
Err(WitnessLengthError::TooShort)
} else if len > 40 + 33 {
Err(WitnessLengthError::TooLong)
} else if witness_version == Fe32::Q && len != 53 && len != 65 {
Err(WitnessLengthError::InvalidSegwitV0)
} else {
Ok(())
}
}
}
#[derive(Debug)]
pub struct SegwitHrpstring<'s> {
hrp: Hrp,
witness_version: Fe32,
data: &'s [u8],
}
impl<'s> SegwitHrpstring<'s> {
#[inline]
pub fn new(s: &'s str) -> Result<Self, SegwitHrpstringError> {
let unchecked = UncheckedHrpstring::new(s)?;
if unchecked.data.is_empty() {
return Err(SegwitHrpstringError::MissingWitnessVersion);
}
let witness_version = Fe32::from_char(unchecked.data[0].into()).unwrap();
if witness_version.to_u8() > 16 {
return Err(SegwitHrpstringError::InvalidWitnessVersion(witness_version));
}
let checked: CheckedHrpstring<'s> = match witness_version {
VERSION_0 => unchecked.validate_and_remove_checksum::<Blech32>()?,
_ => unchecked.validate_and_remove_checksum::<Blech32m>()?,
};
checked.validate_segwit()
}
#[inline]
pub fn new_bech32(s: &'s str) -> Result<Self, SegwitHrpstringError> {
let unchecked = UncheckedHrpstring::new(s)?;
let witness_version = Fe32::from_char(unchecked.data[0].into()).unwrap();
if witness_version.to_u8() > 16 {
return Err(SegwitHrpstringError::InvalidWitnessVersion(witness_version));
}
let checked = unchecked.validate_and_remove_checksum::<Blech32>()?;
checked.validate_segwit()
}
#[inline]
pub fn has_valid_hrp(&self) -> bool { self.hrp().is_valid_segwit() }
#[inline]
pub fn hrp(&self) -> Hrp { self.hrp }
#[inline]
pub fn witness_version(&self) -> Fe32 { self.witness_version }
#[inline]
pub fn byte_iter(&self) -> ByteIter<'_> {
ByteIter { iter: AsciiToFe32Iter { iter: self.data.iter().copied() }.fes_to_bytes() }
}
}
fn check_characters(s: &str) -> Result<usize, CharError> {
use CharError as E;
let mut has_upper = false;
let mut has_lower = false;
let mut req_bech32 = true;
let mut sep_pos = None;
for (n, ch) in s.char_indices().rev() {
if ch == SEP && sep_pos.is_none() {
req_bech32 = false;
sep_pos = Some(n);
}
if req_bech32 {
Fe32::from_char(ch).map_err(|_| E::InvalidChar(ch))?;
}
if ch.is_ascii_uppercase() {
has_upper = true;
} else if ch.is_ascii_lowercase() {
has_lower = true;
}
}
if has_upper && has_lower {
Err(E::MixedCase)
} else if let Some(pos) = sep_pos {
Ok(pos)
} else {
Err(E::MissingSeparator)
}
}
pub struct ByteIter<'s> {
iter: FesToBytes<AsciiToFe32Iter<iter::Copied<slice::Iter<'s, u8>>>>,
}
impl Iterator for ByteIter<'_> {
type Item = u8;
#[inline]
fn next(&mut self) -> Option<u8> { self.iter.next() }
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
}
impl ExactSizeIterator for ByteIter<'_> {
#[inline]
fn len(&self) -> usize { self.iter.len() }
}
struct AsciiToFe32Iter<I: Iterator<Item = u8>> {
iter: I,
}
impl<I> Iterator for AsciiToFe32Iter<I>
where
I: Iterator<Item = u8>,
{
type Item = Fe32;
#[inline]
fn next(&mut self) -> Option<Fe32> { self.iter.next().map(|ch| Fe32::from_char(ch.into()).unwrap()) }
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<I> ExactSizeIterator for AsciiToFe32Iter<I>
where
I: Iterator<Item = u8> + ExactSizeIterator,
{
#[inline]
fn len(&self) -> usize { self.iter.len() }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SegwitHrpstringError {
Unchecked(UncheckedHrpstringError),
MissingWitnessVersion,
InvalidWitnessVersion(Fe32),
Padding(PaddingError),
WitnessLength(WitnessLengthError),
Checksum(ChecksumError),
}
impl fmt::Display for SegwitHrpstringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Unchecked(ref e) => write_err!(f, "parsing unchecked hrpstring failed"; e),
Self::MissingWitnessVersion => write!(f, "the witness version byte is missing"),
Self::InvalidWitnessVersion(fe) => write!(f, "invalid segwit witness version: {}", fe.to_u8()),
Self::Padding(ref e) => write_err!(f, "invalid padding on the witness data"; e),
Self::WitnessLength(ref e) => write_err!(f, "invalid witness length"; e),
Self::Checksum(ref e) => write_err!(f, "invalid checksum"; e),
}
}
}
impl std::error::Error for SegwitHrpstringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Unchecked(ref e) => Some(e),
Self::Padding(ref e) => Some(e),
Self::WitnessLength(ref e) => Some(e),
Self::Checksum(ref e) => Some(e),
Self::MissingWitnessVersion | Self::InvalidWitnessVersion(_) => None,
}
}
}
impl From<UncheckedHrpstringError> for SegwitHrpstringError {
#[inline]
fn from(e: UncheckedHrpstringError) -> Self { Self::Unchecked(e) }
}
impl From<WitnessLengthError> for SegwitHrpstringError {
#[inline]
fn from(e: WitnessLengthError) -> Self { Self::WitnessLength(e) }
}
impl From<PaddingError> for SegwitHrpstringError {
#[inline]
fn from(e: PaddingError) -> Self { Self::Padding(e) }
}
impl From<ChecksumError> for SegwitHrpstringError {
#[inline]
fn from(e: ChecksumError) -> Self { Self::Checksum(e) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CheckedHrpstringError {
Parse(UncheckedHrpstringError),
Checksum(ChecksumError),
}
impl fmt::Display for CheckedHrpstringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Parse(ref e) => write_err!(f, "parse failed"; e),
Self::Checksum(ref e) => write_err!(f, "invalid checksum"; e),
}
}
}
impl std::error::Error for CheckedHrpstringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Parse(ref e) => Some(e),
Self::Checksum(ref e) => Some(e),
}
}
}
impl From<UncheckedHrpstringError> for CheckedHrpstringError {
#[inline]
fn from(e: UncheckedHrpstringError) -> Self { Self::Parse(e) }
}
impl From<ChecksumError> for CheckedHrpstringError {
#[inline]
fn from(e: ChecksumError) -> Self { Self::Checksum(e) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum UncheckedHrpstringError {
Char(CharError),
Hrp(hrp::Error),
}
impl fmt::Display for UncheckedHrpstringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Char(ref e) => write_err!(f, "character error"; e),
Self::Hrp(ref e) => write_err!(f, "invalid human-readable part"; e),
}
}
}
impl std::error::Error for UncheckedHrpstringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Char(ref e) => Some(e),
Self::Hrp(ref e) => Some(e),
}
}
}
impl From<CharError> for UncheckedHrpstringError {
#[inline]
fn from(e: CharError) -> Self { Self::Char(e) }
}
impl From<hrp::Error> for UncheckedHrpstringError {
#[inline]
fn from(e: hrp::Error) -> Self { Self::Hrp(e) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CharError {
MissingSeparator,
NothingAfterSeparator,
InvalidChecksum,
InvalidChecksumLength,
InvalidChar(char),
MixedCase,
}
impl fmt::Display for CharError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::MissingSeparator => write!(f, "missing human-readable separator, \"{}\"", SEP),
Self::NothingAfterSeparator => write!(f, "invalid data - no characters after the separator"),
Self::InvalidChecksum => write!(f, "invalid checksum"),
Self::InvalidChecksumLength => write!(f, "the checksum is not a valid length"),
Self::InvalidChar(n) => write!(f, "invalid character (code={})", n),
Self::MixedCase => write!(f, "mixed-case strings not allowed"),
}
}
}
impl std::error::Error for CharError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::MissingSeparator
| Self::NothingAfterSeparator
| Self::InvalidChecksum
| Self::InvalidChecksumLength
| Self::InvalidChar(_)
| Self::MixedCase => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChecksumError {
InvalidChecksum,
InvalidChecksumLength,
}
impl fmt::Display for ChecksumError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InvalidChecksum => write!(f, "invalid checksum"),
Self::InvalidChecksumLength => write!(f, "the checksum is not a valid length"),
}
}
}
impl std::error::Error for ChecksumError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::InvalidChecksum | Self::InvalidChecksumLength => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PaddingError {
TooMuch,
NonZero,
}
impl fmt::Display for PaddingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::TooMuch => write!(f, "the data payload has too many bits of padding"),
Self::NonZero => write!(f, "the data payload is padded with non-zero bits"),
}
}
}
impl std::error::Error for PaddingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::TooMuch | Self::NonZero => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bip_173_invalid_parsing_fails() {
use UncheckedHrpstringError as E;
let invalid: Vec<(&str, UncheckedHrpstringError)> = vec!(
("\u{20}1nwldj5",
E::Hrp(hrp::Error::InvalidAsciiByte(32))),
("\u{7F}1axkwrx",
E::Hrp(hrp::Error::InvalidAsciiByte(127))),
("\u{80}1eym55h",
E::Hrp(hrp::Error::NonAsciiChar('\u{80}'))),
("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
E::Hrp(hrp::Error::TooLong(84))),
("pzry9x0s0muk",
E::Char(CharError::MissingSeparator)),
("1pzry9x0s0muk",
E::Hrp(hrp::Error::Empty)),
("x1b4n0q5v",
E::Char(CharError::InvalidChar('b'))),
("de1lg7wt\u{ff}",
E::Char(CharError::InvalidChar('\u{ff}'))),
("10a06t8",
E::Hrp(hrp::Error::Empty)),
("1qzzfhee",
E::Hrp(hrp::Error::Empty)),
);
for (s, want) in invalid {
let got = UncheckedHrpstring::new(s).unwrap_err();
assert_eq!(got, want);
}
}
#[test]
fn bip_350_invalid_parsing_fails() {
use UncheckedHrpstringError as E;
let invalid: Vec<(&str, UncheckedHrpstringError)> = vec!(
("\u{20}1xj0phk",
E::Hrp(hrp::Error::InvalidAsciiByte(32))),
("\u{7F}1g6xzxy",
E::Hrp(hrp::Error::InvalidAsciiByte(127))),
("\u{80}1g6xzxy",
E::Hrp(hrp::Error::NonAsciiChar('\u{80}'))),
("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
E::Hrp(hrp::Error::TooLong(84))),
("qyrz8wqd2c9m",
E::Char(CharError::MissingSeparator)),
("1qyrz8wqd2c9m",
E::Hrp(hrp::Error::Empty)),
("y1b0jsk6g",
E::Char(CharError::InvalidChar('b'))),
("lt1igcx5c0",
E::Char(CharError::InvalidChar('i'))),
("mm1crxm3i",
E::Char(CharError::InvalidChar('i'))),
("au1s5cgom",
E::Char(CharError::InvalidChar('o'))),
("16plkw9",
E::Hrp(hrp::Error::Empty)),
("1p2gdwpf",
E::Hrp(hrp::Error::Empty)),
);
for (s, want) in invalid {
let got = UncheckedHrpstring::new(s).unwrap_err();
assert_eq!(got, want);
}
}
#[test]
fn check_hrp_uppercase_returns_lower() {
let addr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
let unchecked = UncheckedHrpstring::new(addr).expect("failed to parse address");
assert_eq!(unchecked.hrp(), Hrp::parse_unchecked("bc"));
}
#[test]
fn check_hrp_max_length() {
let hrps =
"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio";
let hrp = Hrp::parse_unchecked(hrps);
let s = bech32::encode::<Blech32>(hrp, &[]).expect("failed to encode empty buffer");
let unchecked = UncheckedHrpstring::new(&s).expect("failed to parse address");
assert_eq!(unchecked.hrp(), hrp);
}
macro_rules! check_invalid_segwit_addresses {
($($test_name:ident, $reason:literal, $address:literal);* $(;)?) => {
$(
#[test]
fn $test_name() {
let res = SegwitHrpstring::new($address);
if res.is_ok() {
panic!("{} sting should not be valid: {}", $address, $reason);
}
}
)*
}
}
check_invalid_segwit_addresses! {
invalid_segwit_address_0, "missing hrp", "1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq";
invalid_segwit_address_1, "missing data-checksum", "91111";
invalid_segwit_address_2, "invalid witness version", "bc14r0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq";
invalid_segwit_address_3, "invalid checksum length", "bc1q5mdq";
invalid_segwit_address_4, "missing data", "bc1qwf5mdq";
invalid_segwit_address_5, "invalid program length", "bc14r0srrr7xfkvy5l643lydnw9rewf5mdq";
}
}