Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(types): Hrp wraps bech32::Hrp #1413

Merged
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
# Mandatory dependencies
bech32 = { version = "0.9.1", default-features = false }
bech32 = { version = "0.10.0-beta", default-features = false, features = [
"alloc",
] }
bitflags = { version = "2.4.0", default-features = false }
derive_more = { version = "0.99.17", default-features = false, features = [
"from",
Expand Down
93 changes: 26 additions & 67 deletions sdk/src/types/block/address/bech32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use alloc::{
};
use core::str::FromStr;

use bech32::{FromBase32, ToBase32, Variant};
use derive_more::{AsRef, Deref};
use packable::{
error::{UnpackError, UnpackErrorExt},
Expand All @@ -18,67 +17,38 @@ use packable::{

use crate::types::block::{address::Address, ConvertTo, Error};

const HRP_MAX: u8 = 83;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Hrp {
inner: [u8; HRP_MAX as usize],
len: u8,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref)]
#[repr(transparent)]
pub struct Hrp(bech32::Hrp);

impl core::fmt::Debug for Hrp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Hrp")
.field("display", &self.to_string())
.field("inner", &prefix_hex::encode(&self.inner[..self.len as usize]))
.field("len", &self.len)
.field("display", &self.0.to_string())
.field("bytes", &prefix_hex::encode(self.0.byte_iter().collect::<Vec<_>>()))
.field("len", &self.0.len())
kwek20 marked this conversation as resolved.
Show resolved Hide resolved
.finish()
}
}

impl Hrp {
/// Convert a string to an Hrp without checking validity.
pub const fn from_str_unchecked(hrp: &str) -> Self {
let len = hrp.len();
let mut bytes = [0; HRP_MAX as usize];
let hrp = hrp.as_bytes();
let mut i = 0;
while i < len {
bytes[i] = hrp[i];
i += 1;
}
Self {
inner: bytes,
len: len as _,
}
Self(bech32::Hrp::parse_unchecked(hrp))
}
}

impl FromStr for Hrp {
type Err = Error;

fn from_str(hrp: &str) -> Result<Self, Self::Err> {
let len = hrp.len();
if hrp.is_ascii() && len <= HRP_MAX as usize {
let mut bytes = [0; HRP_MAX as usize];
bytes[..len].copy_from_slice(hrp.as_bytes());
Ok(Self {
inner: bytes,
len: len as _,
})
} else {
Err(Error::InvalidBech32Hrp(hrp.to_string()))
}
Ok(Self(bech32::Hrp::parse(hrp)?))
}
}

impl core::fmt::Display for Hrp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let hrp_str = self.inner[..self.len as usize]
.iter()
.map(|b| *b as char)
.collect::<String>();
f.write_str(&hrp_str)
self.0.fmt(f)
}
}

Expand All @@ -88,8 +58,9 @@ impl Packable for Hrp {

#[inline]
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.len.pack(packer)?;
packer.pack_bytes(&self.inner[..self.len as usize])?;
(self.0.len() as u8).pack(packer)?;
// TODO revisit when/if bech32 adds a way to get the bytes without iteration to avoid collecting
packer.pack_bytes(&self.0.byte_iter().collect::<Vec<_>>())?;
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}
Expand All @@ -99,21 +70,15 @@ impl Packable for Hrp {
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
let len = u8::unpack::<_, VERIFY>(unpacker, visitor).coerce()?;

if len > HRP_MAX {
return Err(UnpackError::Packable(Error::InvalidBech32Hrp(
"hrp len above 83".to_string(),
)));
}
let len = u8::unpack::<_, VERIFY>(unpacker, visitor).coerce()? as usize;

let mut bytes = alloc::vec![0u8; len as usize];
let mut bytes = alloc::vec![0u8; len];
unpacker.unpack_bytes(&mut bytes)?;

let mut inner = [0; 83];
inner[..len as usize].copy_from_slice(&bytes);

Ok(Self { inner, len })
Ok(Self(
bech32::Hrp::parse(&String::from_utf8_lossy(&bytes))
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
.map_err(|e| UnpackError::Packable(Error::InvalidBech32Hrp(e)))?,
))
}
}

Expand Down Expand Up @@ -161,14 +126,13 @@ impl FromStr for Bech32Address {
type Err = Error;

fn from_str(address: &str) -> Result<Self, Self::Err> {
match ::bech32::decode(address) {
Ok((hrp, data, _)) => {
let hrp = hrp.parse()?;
let bytes = Vec::<u8>::from_base32(&data).map_err(|_| Error::InvalidAddress)?;
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
Address::unpack_verified(bytes.as_slice(), &())
.map_err(|_| Error::InvalidAddress)
.map(|address| Self { hrp, inner: address })
}
match bech32::decode(address) {
Ok((hrp, bytes)) => Address::unpack_verified(bytes.as_slice(), &())
.map_err(|_| Error::InvalidAddress)
.map(|address| Self {
hrp: Hrp(hrp),
inner: address,
}),
Err(_) => Err(Error::InvalidAddress),
}
}
Expand Down Expand Up @@ -217,12 +181,7 @@ impl core::fmt::Display for Bech32Address {
write!(
f,
"{}",
::bech32::encode(
&self.hrp.to_string(),
self.inner.pack_to_vec().to_base32(),
Variant::Bech32
)
.unwrap()
bech32::encode::<bech32::Bech32>(self.hrp.0, &self.inner.pack_to_vec(),).unwrap()
)
}
}
Expand Down
11 changes: 9 additions & 2 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use alloc::string::{FromUtf8Error, String};
use core::{convert::Infallible, fmt};

use bech32::primitives::hrp::Error as Bech32HrpError;
use crypto::Error as CryptoError;
use prefix_hex::Error as HexError;
use primitive_types::U256;
Expand Down Expand Up @@ -79,7 +80,7 @@ pub enum Error {
InvalidInputKind(u8),
InvalidInputCount(<InputCount as TryFrom<usize>>::Error),
InvalidInputOutputIndex(<OutputIndex as TryFrom<u16>>::Error),
InvalidBech32Hrp(String),
InvalidBech32Hrp(Bech32HrpError),
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
InvalidAddressCapabilitiesCount(<u8 as TryFrom<usize>>::Error),
InvalidBlockWrapperLength(usize),
InvalidStateMetadataLength(<StateMetadataLength as TryFrom<usize>>::Error),
Expand Down Expand Up @@ -216,7 +217,7 @@ impl fmt::Display for Error {
Self::InvalidAddress => write!(f, "invalid address provided"),
Self::InvalidAddressKind(k) => write!(f, "invalid address kind: {k}"),
Self::InvalidAccountIndex(index) => write!(f, "invalid account index: {index}"),
Self::InvalidBech32Hrp(err) => write!(f, "invalid bech32 hrp: {err}"),
Self::InvalidBech32Hrp(e) => write!(f, "invalid bech32 hrp: {e}"),
Self::InvalidAddressCapabilitiesCount(e) => write!(f, "invalid capabilities count: {e}"),
Self::InvalidBlockKind(k) => write!(f, "invalid block kind: {k}"),
Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"),
Expand Down Expand Up @@ -386,6 +387,12 @@ impl fmt::Display for Error {
}
}

impl From<Bech32HrpError> for Error {
fn from(error: Bech32HrpError) -> Self {
Self::InvalidBech32Hrp(error)
}
}

impl From<CryptoError> for Error {
fn from(error: CryptoError) -> Self {
Self::Crypto(error)
Expand Down
12 changes: 8 additions & 4 deletions sdk/tests/types/address/bech32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ fn ctors() {
fn hrp_from_str() {
Hrp::from_str("rms").unwrap();

assert!(matches!(
Hrp::from_str("中國"),
Err(Error::InvalidBech32Hrp(hrp)) if hrp == "中國"
));
assert!(matches!(Hrp::from_str("中國"), Err(Error::InvalidBech32Hrp(_))));
}

#[test]
Expand All @@ -61,6 +58,13 @@ fn hrp_pack_unpack() {
assert_eq!(hrp, Hrp::unpack_verified(packed_hrp.as_slice(), &()).unwrap());
}

#[test]
fn invalid_hrp_unpack() {
let packed_hrp = vec![32, 32, 32]; // invalid HRP: " "

assert!(Hrp::unpack_verified(packed_hrp.as_slice(), &()).is_err());
}

#[test]
fn bech32_into_inner() {
let address = Address::try_from_bech32(ED25519_BECH32).unwrap();
Expand Down
Loading