From 9ca2936590af31d900cf1e89a77bc2e0d432db00 Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Wed, 4 Apr 2018 16:47:08 +0200 Subject: [PATCH 1/5] add u5 and conversion traits --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 30ea9bbf3..e0e283a00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,27 @@ use std::{error, fmt}; use std::str::FromStr; use std::fmt::{Display, Formatter}; +/// Integer in the range `0..32` +#[derive(PartialEq, Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub struct u5(u8); + +/// Parse/convert base32 slice to `Self`. It is the reciprocal of +/// `ToBase32`. +pub trait FromBase32: Sized { + /// The associated error which can be returned from parsing (e.g. because of bad padding). + type Err; + + /// Convert a base32 slice to `Self`. + fn from_base32(b32: &[u5]) -> Result; +} + +/// A trait for converting a value to a type `T`, that represents a `u5` slice. +pub trait ToBase32> { + /// Convert `Self` to base32 slice + fn to_base32(&self) -> T; +} + /// Grouping structure for the human-readable part and the data part /// of decoded Bech32 string. #[derive(PartialEq, Debug, Clone)] From c518fcd02a7f73ae2e969dfca9e396fe7df4db6b Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Fri, 6 Apr 2018 18:32:40 +0200 Subject: [PATCH 2/5] Use u5 where applicable --- src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e0e283a00..c46ead638 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! ```rust //! use bech32::Bech32; //! -//! let b = Bech32::new("bech32".into(), vec![0x00, 0x01, 0x02]).unwrap(); +//! let b = Bech32::new_check_data("bech32".into(), vec![0x00, 0x01, 0x02]).unwrap(); //! let encoded = b.to_string(); //! assert_eq!(encoded, "bech321qpz4nc4pe".to_string()); //! @@ -51,7 +51,7 @@ use std::str::FromStr; use std::fmt::{Display, Formatter}; /// Integer in the range `0..32` -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Debug, Copy, Clone, Default)] #[allow(non_camel_case_types)] pub struct u5(u8); @@ -65,12 +65,22 @@ pub trait FromBase32: Sized { fn from_base32(b32: &[u5]) -> Result; } -/// A trait for converting a value to a type `T`, that represents a `u5` slice. +/// A trait for converting a value to a type `T` that represents a `u5` slice. pub trait ToBase32> { /// Convert `Self` to base32 slice fn to_base32(&self) -> T; } +/// A trait to convert between u8 arrays and u5 arrays without changing the content of the elements, +/// but checking that they are in range. +pub trait CheckBase32> { + /// Error type if conversion fails + type Err; + + /// Check if all values are in range and return array-like struct of `u5` values + fn check_base32(self) -> Result; +} + /// Grouping structure for the human-readable part and the data part /// of decoded Bech32 string. #[derive(PartialEq, Debug, Clone)] @@ -78,34 +88,73 @@ pub struct Bech32 { /// Human-readable part hrp: String, /// Data payload - data: Vec + data: Vec +} + +impl u5 { + /// Convert a `u8` to `u5` if in range, return `Error` otherwise + pub fn try_from_u8(value: u8) -> Result { + if value > 31 { + Err(Error::InvalidData(value)) + } else { + Ok(u5(value)) + } + } +} + +impl Into for u5 { + fn into(self) -> u8 { + self.0 + } +} + +impl AsRef for u5 { + fn as_ref(&self) -> &u8 { + &self.0 + } +} + +// TODO: try to implement conversion by typecasting, see https://www.reddit.com/r/rust/comments/88xvgg/hey_rustaceans_got_an_easy_question_ask_here/dwvk0w9/ +impl<'f, T: AsRef<[u8]>> CheckBase32> for T { + type Err = Error; + + fn check_base32(self) -> Result, Self::Err> { + self.as_ref().iter().map(|x| u5::try_from_u8(*x)).collect::, Error>>() + } } + impl Bech32 { /// Constructs a `Bech32` struct if the result can be encoded as a bech32 string. - pub fn new(hrp: String, data: Vec) -> Result { + pub fn new(hrp: String, data: Vec) -> Result { if hrp.is_empty() { return Err(Error::InvalidLength) } - if let Some(bad_byte) = data.iter().find(|&&x| x >= 32) { - return Err(Error::InvalidData(*bad_byte)); - } Ok(Bech32 {hrp: hrp, data: data}) } + /// Constructs a `Bech32` struct if the result can be encoded as a bech32 string. It uses + /// `data` that is not range checked yet and as a result may return `Err(Error::InvalidData)`. + /// + /// This function currently allocates memory for the checked data part. + /// See [issue #19](https://github.com/rust-bitcoin/rust-bech32/issues/19). + pub fn new_check_data(hrp: String, data: Vec) -> Result { + Self::new(hrp, data.check_base32()?) + } + /// Returns the human readable part pub fn hrp(&self) -> &str { &self.hrp } /// Returns the data part as `[u8]` but only using 5 bits per byte - pub fn data(&self) -> &[u8] { + pub fn data(&self) -> &[u5] { &self.data } /// Destructures the `Bech32` struct into its parts - pub fn into_parts(self) -> (String, Vec) { + pub fn into_parts(self) -> (String, Vec) { (self.hrp, self.data) } @@ -153,7 +202,7 @@ impl Bech32 { } // Check data payload - let mut data_bytes: Vec = Vec::new(); + let mut data_bytes: Vec = Vec::new(); for b in raw_data.bytes() { // Aphanumeric only if !((b >= b'0' && b <= b'9') || (b >= b'A' && b <= b'Z') || (b >= b'a' && b <= b'z')) { @@ -177,7 +226,9 @@ impl Bech32 { b }; - data_bytes.push(CHARSET_REV[c as usize] as u8); + data_bytes.push(u5::try_from_u8(CHARSET_REV[c as usize] as u8).expect( + "range was already checked above" + )); } // Ensure no mixed case @@ -212,7 +263,7 @@ impl Display for Bech32 { "{}{}{}", self.hrp, SEP, - data_part.map(|p| CHARSET[*p as usize]).collect::() + data_part.map(|p| CHARSET[*p.as_ref() as usize]).collect::() ) } } @@ -229,43 +280,43 @@ impl FromStr for Bech32 { } } -fn create_checksum(hrp: &[u8], data: &[u8]) -> Vec { - let mut values: Vec = hrp_expand(hrp); +fn create_checksum(hrp: &[u8], data: &[u5]) -> Vec { + let mut values: Vec = hrp_expand(hrp); values.extend_from_slice(data); // Pad with 6 zeros - values.extend_from_slice(&[0u8; 6]); + values.extend_from_slice(&[u5::try_from_u8(0).unwrap(); 6]); let plm: u32 = polymod(&values) ^ 1; - let mut checksum: Vec = Vec::new(); + let mut checksum: Vec = Vec::new(); for p in 0..6 { - checksum.push(((plm >> (5 * (5 - p))) & 0x1f) as u8); + checksum.push(u5::try_from_u8(((plm >> (5 * (5 - p))) & 0x1f) as u8).unwrap()); } checksum } -fn verify_checksum(hrp: &[u8], data: &[u8]) -> bool { +fn verify_checksum(hrp: &[u8], data: &[u5]) -> bool { let mut exp = hrp_expand(hrp); exp.extend_from_slice(data); polymod(&exp) == 1u32 } -fn hrp_expand(hrp: &[u8]) -> Vec { - let mut v: Vec = Vec::new(); +fn hrp_expand(hrp: &[u8]) -> Vec { + let mut v: Vec = Vec::new(); for b in hrp { - v.push(*b >> 5); + v.push(u5::try_from_u8(*b >> 5).expect("can't be out of range, max. 7")); } - v.push(0); + v.push(u5::try_from_u8(0).unwrap()); for b in hrp { - v.push(*b & 0x1f); + v.push(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 31")); } v } -fn polymod(values: &[u8]) -> u32 { +fn polymod(values: &[u5]) -> u32 { let mut chk: u32 = 1; let mut b: u8; for v in values { b = (chk >> 25) as u8; - chk = (chk & 0x1ffffff) << 5 ^ (u32::from(*v)); + chk = (chk & 0x1ffffff) << 5 ^ (*v.as_ref() as u32); for i in 0..5 { if (b >> i) & 1 == 1 { chk ^= GEN[i] @@ -397,21 +448,25 @@ mod tests { use Bech32; use Error; use convert_bits; + use CheckBase32; #[test] fn new_checks() { - assert!(Bech32::new("test".into(), vec![1, 2, 3, 4]).is_ok()); - assert_eq!(Bech32::new("".into(), vec![1, 2, 3, 4]), Err(Error::InvalidLength)); - assert_eq!(Bech32::new("test".into(), vec![30, 31, 35, 20]), Err(Error::InvalidData(35))); + assert!(Bech32::new_check_data("test".into(), vec![1, 2, 3, 4]).is_ok()); + assert_eq!(Bech32::new_check_data("".into(), vec![1, 2, 3, 4]), Err(Error::InvalidLength)); + assert_eq!(Bech32::new_check_data("test".into(), vec![30, 31, 35, 20]), Err(Error::InvalidData(35))); - let both = Bech32::new("".into(), vec![30, 31, 35, 20]); + let both = Bech32::new_check_data("".into(), vec![30, 31, 35, 20]); assert!(both == Err(Error::InvalidLength) || both == Err(Error::InvalidData(35))); + + assert!(Bech32::new("test".into(), [1u8, 2, 3, 4].check_base32().unwrap()).is_ok()); + assert_eq!(Bech32::new("".into(), [1u8, 2, 3, 4].check_base32().unwrap()), Err(Error::InvalidLength)); } #[test] fn getters() { let bech: Bech32 = "BC1SW50QA3JX3S".parse().unwrap(); - let data: Vec = vec![16, 14, 20, 15, 0]; + let data = [16, 14, 20, 15, 0].check_base32().unwrap(); assert_eq!(bech.hrp(), "bc"); assert_eq!( bech.data(), From e3f5d0012aad5b4fdab1533689a64db1d5770f40 Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Fri, 6 Apr 2018 22:15:25 +0200 Subject: [PATCH 3/5] Implement From- and ToBase32 + add to_u8 function to u5 --- src/lib.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c46ead638..074e10e24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,22 @@ //! let c = encoded.parse::(); //! assert_eq!(b, c.unwrap()); //! ``` +//! +//! If the data is already range-checked the `Bech32::new` function can be used which will never +//! return `Err(Error::InvalidData)`. +//! +//! ```rust +//! use bech32::{Bech32, u5, ToBase32}; +//! +//! // converts base256 data to base32 and adds padding if needed +//! let checked_data: Vec = [0xb4, 0xff, 0xa5].to_base32(); +//! +//! let b = Bech32::new("bech32".into(), checked_data).expect("hrp is not empty"); +//! let encoded = b.to_string(); +//! +//! assert_eq!(encoded, "bech321knl623tk6v7".to_string()); +//! ``` +//! #![deny(missing_docs)] #![deny(non_upper_case_globals)] @@ -51,7 +67,7 @@ use std::str::FromStr; use std::fmt::{Display, Formatter}; /// Integer in the range `0..32` -#[derive(PartialEq, Debug, Copy, Clone, Default)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, Default, PartialOrd, Ord)] #[allow(non_camel_case_types)] pub struct u5(u8); @@ -100,6 +116,11 @@ impl u5 { Ok(u5(value)) } } + + /// Returns a copy of the underlying `u8` value + pub fn to_u8(&self) -> u8 { + self.0 + } } impl Into for u5 { @@ -114,7 +135,6 @@ impl AsRef for u5 { } } -// TODO: try to implement conversion by typecasting, see https://www.reddit.com/r/rust/comments/88xvgg/hey_rustaceans_got_an_easy_question_ask_here/dwvk0w9/ impl<'f, T: AsRef<[u8]>> CheckBase32> for T { type Err = Error; @@ -123,6 +143,26 @@ impl<'f, T: AsRef<[u8]>> CheckBase32> for T { } } +impl FromBase32 for Vec { + type Err = Error; + + /// Convert base32 to base256, removes null-padding if present, returns + /// `Err(Error::InvalidPadding)` if padding bits are unequal `0` + fn from_base32(b32: &[u5]) -> Result { + convert_bits(b32, 5, 8, false) + } +} + +impl> ToBase32> for T { + /// Convert base256 to base32, adds padding if necessary + fn to_base32(&self) -> Vec { + convert_bits(self.as_ref(), 8, 5, true).expect( + "both error conditions are impossible (InvalidPadding, InvalidData)" + ).check_base32().expect( + "after conversion all elements are in range" + ) + } +} impl Bech32 { /// Constructs a `Bech32` struct if the result can be encoded as a bech32 string. @@ -401,6 +441,10 @@ impl error::Error for Error { /// Convert between bit sizes /// +/// # Errors +/// * `Error::InvalidData` if any element of `data` is out of range +/// * `Error::InvalidPadding` if `pad == false` and the padding bits are not `0` +/// /// # Panics /// Function will panic if attempting to convert `from` or `to` a bit size that /// is 0 or larger than 8 bits. @@ -412,7 +456,9 @@ impl error::Error for Error { /// let base5 = convert_bits(&[0xff], 8, 5, true); /// assert_eq!(base5.unwrap(), vec![0x1f, 0x1c]); /// ``` -pub fn convert_bits(data: &[u8], from: u32, to: u32, pad: bool) -> Result, Error> { +pub fn convert_bits(data: &[T], from: u32, to: u32, pad: bool) -> Result, Error> + where T: Into + Copy +{ if from > 8 || to > 8 || from == 0 || to == 0 { panic!("convert_bits `from` and `to` parameters 0 or greater than 8"); } @@ -421,7 +467,7 @@ pub fn convert_bits(data: &[u8], from: u32, to: u32, pad: bool) -> Result = Vec::new(); let maxv: u32 = (1<::into(*value) as u32; if (v >> from) != 0 { // Input value exceeds `from` bit size return Err(Error::InvalidData(v as u8)) @@ -586,4 +632,27 @@ mod tests { Err(Error::InvalidLength) ); } + + #[test] + fn check_base32() { + assert!([0u8, 1, 2, 30, 31].check_base32().is_ok()); + assert!([0u8, 1, 2, 30, 31, 32].check_base32().is_err()); + assert!([0u8, 1, 2, 30, 31, 255].check_base32().is_err()); + } + + #[test] + fn from_base32() { + use FromBase32; + assert_eq!(Vec::from_base32(&[0x1f, 0x1c].check_base32().unwrap()), Ok(vec![0xff])); + assert_eq!( + Vec::from_base32(&[0x1f, 0x1f].check_base32().unwrap()), + Err(Error::InvalidPadding) + ); + } + + #[test] + fn to_base32() { + use ToBase32; + assert_eq!([0xffu8].to_base32(), [0x1f, 0x1c].check_base32().unwrap()); + } } From 31e2d46e95cf04382bb02a834a1f22c3574cd38d Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Sun, 15 Apr 2018 23:55:15 +0200 Subject: [PATCH 4/5] resolve clippy hints --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 074e10e24..8fe182bc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,7 +356,7 @@ fn polymod(values: &[u5]) -> u32 { let mut b: u8; for v in values { b = (chk >> 25) as u8; - chk = (chk & 0x1ffffff) << 5 ^ (*v.as_ref() as u32); + chk = (chk & 0x1ffffff) << 5 ^ (u32::from(*v.as_ref())); for i in 0..5 { if (b >> i) & 1 == 1 { chk ^= GEN[i] @@ -467,7 +467,7 @@ pub fn convert_bits(data: &[T], from: u32, to: u32, pad: bool) -> Result = Vec::new(); let maxv: u32 = (1<::into(*value) as u32; + let v: u32 = u32::from(Into::::into(*value)); if (v >> from) != 0 { // Input value exceeds `from` bit size return Err(Error::InvalidData(v as u8)) From ea4a94718257de053ab9574b1d2a2a16f5026025 Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Mon, 30 Apr 2018 22:57:38 +0200 Subject: [PATCH 5/5] Increase version number and update README * set version to 0.4.0 * use examples from rustdoc in README --- Cargo.toml | 2 +- README.md | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16525279a..c8332a475 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bech32" -version = "0.3.3" +version = "0.4.0" authors = ["Clark Moody"] repository = "https://github.com/rust-bitcoin/rust-bech32" description = "Encodes and decodes the Bech32 format" diff --git a/README.md b/README.md index 8a4e5fa90..5b585c7dd 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,29 @@ Rust implementation of the Bech32 encoding format described in [BIP-0173](https: Bitcoin-specific address encoding is handled by the `bitcoin-bech32` crate. -## Example +## Examples ```rust use bech32::Bech32; -let b = Bech32::new("bech32".into(), vec![0x00, 0x01, 0x02]).unwrap(); +let b = Bech32::new_check_data("bech32".into(), vec![0x00, 0x01, 0x02]).unwrap(); let encoded = b.to_string(); assert_eq!(encoded, "bech321qpz4nc4pe".to_string()); let c = encoded.parse::(); assert_eq!(b, c.unwrap()); ``` + +If the data is already range-checked the `Bech32::new` function can be used which will never +return `Err(Error::InvalidData)`. + +```rust +use bech32::{Bech32, u5, ToBase32}; + +// converts base256 data to base32 and adds padding if needed +let checked_data: Vec = [0xb4, 0xff, 0xa5].to_base32(); + +let b = Bech32::new("bech32".into(), checked_data).expect("hrp is not empty"); +let encoded = b.to_string(); + +assert_eq!(encoded, "bech321knl623tk6v7".to_string()); +``` \ No newline at end of file