#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(bench, feature(test))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(missing_docs)]
#[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::gf32::Fe32,
crate::primitives::hrp::Hrp,
crate::primitives::iter::{ByteIterExt, Fe32IterExt},
crate::primitives::{Bech32, Bech32m, NoChecksum},
};
#[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)?;
if let Err(e) = unchecked.validate_checksum::<Bech32m>() {
if !unchecked.has_valid_checksum::<Bech32>() {
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) }
}
#[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 {
use super::*;
use crate::{Bech32, Bech32m};
#[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 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());
}
}