#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(bench, feature(test))]
#![deny(missing_docs)]
#![allow(clippy::suspicious_arithmetic_impl)] #![allow(clippy::suspicious_op_assign_impl)]
#[cfg(bench)]
extern crate test;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate core;
mod error;
pub mod hrp;
pub mod primitives;
pub mod segwit;
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::{string::String, vec::Vec};
use core::fmt;
use crate::error::write_err;
#[cfg(doc)]
use crate::primitives::decode::CheckedHrpstring;
use crate::primitives::decode::CodeLengthError;
#[cfg(feature = "alloc")]
use crate::primitives::decode::{ChecksumError, UncheckedHrpstring, UncheckedHrpstringError};
#[rustfmt::skip] #[doc(inline)]
pub use {
crate::primitives::checksum::Checksum,
crate::primitives::correction::CorrectableError,
crate::primitives::gf32::Fe32,
crate::primitives::gf32_ext::{Fe1024, Fe32768},
crate::primitives::hrp::Hrp,
crate::primitives::iter::{ByteIterExt, Fe32IterExt},
crate::primitives::{Bech32, Bech32m, NoChecksum},
};
#[cfg(feature = "alloc")]
pub use crate::primitives::checksum::PrintImpl;
#[cfg(not(test))]
const BUF_LENGTH: usize = 1024;
#[cfg(test)]
const BUF_LENGTH: usize = 10;
#[cfg(feature = "alloc")]
#[inline]
pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
let unchecked = UncheckedHrpstring::new(s)?;
match unchecked.validate_checksum::<Bech32m>() {
Ok(_) => {}
Err(ChecksumError::InvalidResidue(ref res_err)) if res_err.matches_bech32_checksum() => {}
Err(e) => {
return Err(DecodeError::Checksum(e));
}
}
let checked = unchecked.remove_checksum::<Bech32m>();
Ok((checked.hrp(), checked.byte_iter().collect()))
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
encode_lower::<Ck>(hrp, data)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
let mut buf = String::new();
encode_lower_to_fmt::<Ck, String>(&mut buf, hrp, data)?;
Ok(buf)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
let mut buf = String::new();
encode_upper_to_fmt::<Ck, String>(&mut buf, hrp, data)?;
Ok(buf)
}
#[inline]
pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
fmt: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeError> {
encode_lower_to_fmt::<Ck, W>(fmt, hrp, data)
}
#[inline]
pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
fmt: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeError> {
let _ = encoded_length::<Ck>(hrp, data)?;
let mut buf = [0u8; BUF_LENGTH];
let mut pos = 0;
let iter = data.iter().copied().bytes_to_fes();
let chars = iter.with_checksum::<Ck>(&hrp).chars();
for c in chars {
buf[pos] = c as u8;
pos += 1;
if pos == BUF_LENGTH {
let s = core::str::from_utf8(&buf).expect("we only write ASCII");
fmt.write_str(s)?;
pos = 0;
}
}
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
fmt.write_str(s)?;
Ok(())
}
#[inline]
pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
fmt: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeError> {
let _ = encoded_length::<Ck>(hrp, data)?;
let mut buf = [0u8; BUF_LENGTH];
let mut pos = 0;
let iter = data.iter().copied().bytes_to_fes();
let chars = iter.with_checksum::<Ck>(&hrp).chars();
for c in chars {
buf[pos] = c.to_ascii_uppercase() as u8;
pos += 1;
if pos == BUF_LENGTH {
let s = core::str::from_utf8(&buf).expect("we only write ASCII");
fmt.write_str(s)?;
pos = 0;
}
}
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
fmt.write_str(s)?;
Ok(())
}
#[cfg(feature = "std")]
#[inline]
pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
w: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeIoError> {
encode_lower_to_writer::<Ck, W>(w, hrp, data)
}
#[cfg(feature = "std")]
#[inline]
pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
w: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeIoError> {
let _ = encoded_length::<Ck>(hrp, data)?;
let mut buf = [0u8; BUF_LENGTH];
let mut pos = 0;
let iter = data.iter().copied().bytes_to_fes();
let chars = iter.with_checksum::<Ck>(&hrp).chars();
for c in chars {
buf[pos] = c as u8;
pos += 1;
if pos == BUF_LENGTH {
w.write_all(&buf)?;
pos = 0;
}
}
w.write_all(&buf[..pos])?;
Ok(())
}
#[cfg(feature = "std")]
#[inline]
pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
w: &mut W,
hrp: Hrp,
data: &[u8],
) -> Result<(), EncodeIoError> {
let _ = encoded_length::<Ck>(hrp, data)?;
let mut buf = [0u8; BUF_LENGTH];
let mut pos = 0;
let iter = data.iter().copied().bytes_to_fes();
let chars = iter.with_checksum::<Ck>(&hrp).chars();
for c in chars {
buf[pos] = c.to_ascii_uppercase() as u8;
pos += 1;
if pos == BUF_LENGTH {
w.write_all(&buf)?;
pos = 0;
}
}
w.write_all(&buf[..pos])?;
Ok(())
}
pub fn encoded_length<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<usize, CodeLengthError> {
let iter = data.iter().copied().bytes_to_fes();
let len = hrp.len() + 1 + iter.len() + Ck::CHECKSUM_LENGTH; if len > Ck::CODE_LENGTH {
Err(CodeLengthError { encoded_length: len, code_length: Ck::CODE_LENGTH })
} else {
Ok(len)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeError {
Parse(UncheckedHrpstringError),
Checksum(ChecksumError),
}
#[cfg(feature = "alloc")]
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use DecodeError::*;
match *self {
Parse(ref e) => write_err!(f, "parsing failed"; e),
Checksum(ref e) => write_err!(f, "no valid bech32 or bech32m checksum"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use DecodeError::*;
match *self {
Parse(ref e) => Some(e),
Checksum(ref e) => Some(e),
}
}
}
#[cfg(feature = "alloc")]
impl From<UncheckedHrpstringError> for DecodeError {
#[inline]
fn from(e: UncheckedHrpstringError) -> Self { Self::Parse(e) }
}
#[cfg(feature = "alloc")]
impl From<ChecksumError> for DecodeError {
#[inline]
fn from(e: ChecksumError) -> Self { Self::Checksum(e) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncodeError {
TooLong(CodeLengthError),
Fmt(fmt::Error),
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use EncodeError::*;
match *self {
TooLong(ref e) => write_err!(f, "encode error"; e),
Fmt(ref e) => write_err!(f, "encode to formatter failed"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use EncodeError::*;
match *self {
TooLong(ref e) => Some(e),
Fmt(ref e) => Some(e),
}
}
}
impl From<CodeLengthError> for EncodeError {
#[inline]
fn from(e: CodeLengthError) -> Self { Self::TooLong(e) }
}
impl From<fmt::Error> for EncodeError {
#[inline]
fn from(e: fmt::Error) -> Self { Self::Fmt(e) }
}
#[cfg(feature = "std")]
#[derive(Debug)]
#[non_exhaustive]
pub enum EncodeIoError {
TooLong(CodeLengthError),
Write(std::io::Error),
}
#[cfg(feature = "std")]
impl fmt::Display for EncodeIoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use EncodeIoError::*;
match *self {
TooLong(ref e) => write_err!(f, "encode error"; e),
Write(ref e) => write_err!(f, "encode to writer failed"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeIoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use EncodeIoError::*;
match *self {
TooLong(ref e) => Some(e),
Write(ref e) => Some(e),
}
}
}
#[cfg(feature = "std")]
impl From<CodeLengthError> for EncodeIoError {
#[inline]
fn from(e: CodeLengthError) -> Self { Self::TooLong(e) }
}
#[cfg(feature = "std")]
impl From<std::io::Error> for EncodeIoError {
#[inline]
fn from(e: std::io::Error) -> Self { Self::Write(e) }
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
#[cfg(feature = "std")]
use std::error::Error;
use super::*;
#[rustfmt::skip]
const DATA: [u8; 20] = [
0xff, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4,
0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23,
0xf1, 0x43, 0x3b, 0xd6,
];
#[test]
fn errors_have_non_empty_display_and_source() {
let decode_err = decode("test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nny")
.expect_err("checksum error expected");
assert!(!decode_err.to_string().is_empty());
let too_long_data = [0_u8; 632];
let encode_err = encode::<Bech32m>(Hrp::parse_unchecked("abcde"), &too_long_data)
.expect_err("too long error expected");
assert!(!encode_err.to_string().is_empty());
#[cfg(feature = "std")]
{
assert!(decode_err.source().is_some());
assert!(encode_err.source().is_some());
struct BrokenWriter;
impl std::io::Write for BrokenWriter {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"mutation regression test write failure",
))
}
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
}
let io_err = encode_to_writer::<Bech32, _>(
&mut BrokenWriter,
Hrp::parse_unchecked("test"),
&DATA,
)
.expect_err("writer error expected");
assert!(!io_err.to_string().is_empty());
assert!(io_err.source().is_some());
}
}
#[test]
fn encode_bech32m() {
let hrp = Hrp::parse_unchecked("test");
let got = encode::<Bech32m>(hrp, &DATA).expect("failed to encode");
let want = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky";
assert_eq!(got, want);
}
#[test]
fn encode_bech32_lower() {
let hrp = Hrp::parse_unchecked("test");
let got = encode_lower::<Bech32>(hrp, &DATA).expect("failed to encode");
let want = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nnx";
assert_eq!(got, want);
}
#[test]
#[cfg(feature = "std")]
fn encode_bech32_lower_to_writer() {
let hrp = Hrp::parse_unchecked("test");
let mut buf = Vec::new();
encode_lower_to_writer::<Bech32, _>(&mut buf, hrp, &DATA).expect("failed to encode");
let got = std::str::from_utf8(&buf).expect("ascii is valid utf8");
let want = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nnx";
assert_eq!(got, want);
}
#[test]
fn encode_bech32_upper() {
let hrp = Hrp::parse_unchecked("test");
let got = encode_upper::<Bech32>(hrp, &DATA).expect("failed to encode");
let want = "TEST1LU08D6QEJXTDG4Y5R3ZARVARY0C5XW7KW79NNX";
assert_eq!(got, want);
}
#[test]
#[cfg(feature = "std")]
fn encode_bech32_upper_to_writer() {
let hrp = Hrp::parse_unchecked("test");
let mut buf = Vec::new();
encode_upper_to_writer::<Bech32, _>(&mut buf, hrp, &DATA).expect("failed to encode");
let got = std::str::from_utf8(&buf).expect("ascii is valid utf8");
let want = "TEST1LU08D6QEJXTDG4Y5R3ZARVARY0C5XW7KW79NNX";
assert_eq!(got, want);
}
#[test]
fn decode_bech32m() {
let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky";
let (hrp, data) = decode(s).expect("failed to encode");
assert_eq!(hrp, Hrp::parse_unchecked("test"));
assert_eq!(data, DATA);
}
#[test]
fn decode_bech32_lower() {
let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kw79nnx";
let (hrp, data) = decode(s).expect("failed to encode");
assert_eq!(hrp, Hrp::parse_unchecked("test"));
assert_eq!(data, DATA);
}
#[test]
fn decode_bech32_upper() {
let s = "TEST1LU08D6QEJXTDG4Y5R3ZARVARY0C5XW7KW79NNX";
let (hrp, data) = decode(s).expect("failed to encode");
assert_eq!(hrp, Hrp::parse_unchecked("TEST"));
assert_eq!(data, DATA);
}
#[test]
fn encoded_length_works() {
let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky";
let (hrp, data) = decode(s).expect("valid string");
let encoded = encode::<Bech32m>(hrp, &data).expect("valid data");
let want = encoded.len();
let got = encoded_length::<Bech32m>(hrp, &data).expect("encoded length");
assert_eq!(got, want);
}
#[test]
fn can_encode_maximum_length_string() {
let data = [0_u8; 632];
let hrp = Hrp::parse_unchecked("abcd");
let s = encode::<Bech32m>(hrp, &data).expect("valid data");
assert_eq!(s.len(), 1023);
}
#[test]
fn can_not_encode_string_too_long() {
let data = [0_u8; 632];
let hrp = Hrp::parse_unchecked("abcde");
match encode::<Bech32m>(hrp, &data) {
Ok(_) => panic!("false positive"),
Err(EncodeError::TooLong(CodeLengthError { encoded_length, code_length: _ })) => {
assert_eq!(encoded_length, 1024)
}
_ => panic!("false negative"),
}
}
#[test]
fn can_decode_segwit_too_long_string() {
let s = "abcd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrw9z3s";
assert!(decode(s).is_ok());
}
}
#[cfg(bench)]
mod benches {
use test::{black_box, Bencher};
use crate::{Bech32, Bech32m};
#[bench]
fn bech32_parse_address(bh: &mut Bencher) {
let addr = black_box("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq");
bh.iter(|| {
let tuple = crate::decode(&addr).expect("address is well formed");
black_box(&tuple);
})
}
#[bench]
fn bech32m_parse_address(bh: &mut Bencher) {
let addr = black_box("bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297");
bh.iter(|| {
let tuple = crate::decode(&addr).expect("address is well formed");
black_box(&tuple);
})
}
#[bench]
fn encode_bech32_address(bh: &mut Bencher) {
let addr = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq";
let (hrp, data) = crate::decode(&addr).expect("address is well formed");
bh.iter(|| {
let s = crate::encode::<Bech32>(hrp, &data).expect("failed to encode");
black_box(&s);
});
}
#[bench]
fn encode_to_fmt_bech32_address(bh: &mut Bencher) {
let addr = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq";
let (hrp, data) = crate::decode(&addr).expect("address is well formed");
let mut buf = String::with_capacity(64);
bh.iter(|| {
let res =
crate::encode_to_fmt::<Bech32, _>(&mut buf, hrp, &data).expect("failed to encode");
black_box(&res);
});
}
#[bench]
fn encode_bech32m_address(bh: &mut Bencher) {
let addr = "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297";
let (hrp, data) = crate::decode(&addr).expect("address is well formed");
bh.iter(|| {
let s = crate::encode::<Bech32m>(hrp, &data).expect("failed to encode");
black_box(&s);
});
}
#[bench]
fn encode_to_fmt_bech32m_address(bh: &mut Bencher) {
let addr = "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297";
let (hrp, data) = crate::decode(&addr).expect("address is well formed");
let mut buf = String::with_capacity(64);
bh.iter(|| {
let res =
crate::encode_to_fmt::<Bech32, _>(&mut buf, hrp, &data).expect("failed to encode");
black_box(&res);
});
}
}