diff --git a/src/lib.rs b/src/lib.rs index ac47c06a6..4e09c27ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,12 +182,12 @@ impl<'a> Bech32Writer<'a> { /// Write out the checksum at the end. If this method isn't called this will happen on drop. pub fn finalize(mut self) -> fmt::Result { - self.inner_finalize()?; + self.write_checksum()?; mem::forget(self); Ok(()) } - fn inner_finalize(&mut self) -> fmt::Result { + fn write_checksum(&mut self) -> fmt::Result { // Pad with 6 zeros for _ in 0..CHECKSUM_LENGTH { self.polymod_step(u5(0)) @@ -203,6 +203,7 @@ impl<'a> Bech32Writer<'a> { Ok(()) } } + impl<'a> WriteBase32 for Bech32Writer<'a> { type Err = fmt::Error; @@ -215,7 +216,7 @@ impl<'a> WriteBase32 for Bech32Writer<'a> { impl<'a> Drop for Bech32Writer<'a> { fn drop(&mut self) { - self.inner_finalize() + self.write_checksum() .expect("Unhandled error writing the checksum on drop.") } } @@ -421,6 +422,32 @@ pub fn encode_to_fmt>( })?) } +/// Encode a bech32 payload without a checksum to an [fmt::Write]. +/// This method is intended for implementing traits from [std::fmt]. +/// +/// # Errors +/// * If [check_hrp] returns an error for the given HRP. +/// * If `fmt` fails on write +/// # Deviations from standard +/// * No length limits are enforced for the data part +pub fn encode_without_checksum_to_fmt>( + fmt: &mut fmt::Write, + hrp: &str, + data: T, +) -> Result<(), Error> { + let hrp = match check_hrp(hrp)? { + Case::Upper => Cow::Owned(hrp.to_lowercase()), + Case::Lower | Case::None => Cow::Borrowed(hrp), + }; + + fmt.write_str(&hrp)?; + fmt.write_char(SEP)?; + for b in data.as_ref() { + fmt.write_char(b.to_char())?; + } + Ok(()) +} + /// Used for encode/decode operations for the two variants of Bech32 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Variant { @@ -463,15 +490,44 @@ pub fn encode>(hrp: &str, data: T, variant: Variant) -> Result>(hrp: &str, data: T) -> Result { + let mut buf = String::new(); + encode_without_checksum_to_fmt(&mut buf, hrp, data)?; + Ok(buf) +} + /// Decode a bech32 string into the raw HRP and the data bytes. /// -/// Returns the HRP in lowercase.. +/// Returns the HRP in lowercase, the data with the checksum removed, and the encoding. pub fn decode(s: &str) -> Result<(String, Vec, Variant), Error> { - // Ensure overall length is within bounds - if s.len() < CHECKSUM_LENGTH + 2 { + let (hrp_lower, mut data) = decode_without_checksum(s)?; + if data.len() < CHECKSUM_LENGTH { return Err(Error::InvalidLength); } + // Ensure checksum + match verify_checksum(hrp_lower.as_bytes(), &data) { + Some(variant) => { + // Remove checksum from data payload + let dbl: usize = data.len(); + data.truncate(dbl - CHECKSUM_LENGTH); + + Ok((hrp_lower, data, variant)) + } + None => Err(Error::InvalidChecksum), + } +} + +/// Decode a bech32 string into the raw HRP and the data bytes, assuming no checksum. +/// +/// Returns the HRP in lowercase and the data with the checksum removed. +pub fn decode_without_checksum(s: &str) -> Result<(String, Vec), Error> { // Split at separator and check for two pieces let (raw_hrp, raw_data) = match s.rfind(SEP) { None => return Err(Error::MissingSeparator), @@ -480,9 +536,6 @@ pub fn decode(s: &str) -> Result<(String, Vec, Variant), Error> { (hrp, &data[1..]) } }; - if raw_data.len() < CHECKSUM_LENGTH { - return Err(Error::InvalidLength); - } let mut case = check_hrp(raw_hrp)?; let hrp_lower = match case { @@ -492,7 +545,7 @@ pub fn decode(s: &str) -> Result<(String, Vec, Variant), Error> { }; // Check data payload - let mut data = raw_data + let data = raw_data .chars() .map(|c| { // Only check if c is in the ASCII range, all invalid ASCII @@ -527,17 +580,7 @@ pub fn decode(s: &str) -> Result<(String, Vec, Variant), Error> { }) .collect::, Error>>()?; - // Ensure checksum - match verify_checksum(hrp_lower.as_bytes(), &data) { - Some(variant) => { - // Remove checksum from data payload - let dbl: usize = data.len(); - data.truncate(dbl - CHECKSUM_LENGTH); - - Ok((hrp_lower, data, variant)) - } - None => Err(Error::InvalidChecksum), - } + Ok((hrp_lower, data)) } fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option { @@ -801,6 +844,8 @@ mod tests { Error::InvalidLength), ("1p2gdwpf", Error::InvalidLength), + ("bc1p2", + Error::InvalidLength), ); for p in pairs { let (s, expected_error) = p; @@ -930,7 +975,7 @@ mod tests { } #[test] - fn writer() { + fn write_with_checksum() { let hrp = "lnbc"; let data = "Hello World!".as_bytes().to_base32(); @@ -947,7 +992,23 @@ mod tests { } #[test] - fn write_on_drop() { + fn write_without_checksum() { + let hrp = "lnbc"; + let data = "Hello World!".as_bytes().to_base32(); + + let mut written_str = String::new(); + { + let mut writer = Bech32Writer::new(hrp, Variant::Bech32, &mut written_str).unwrap(); + writer.write(&data).unwrap(); + } + + let encoded_str = encode_without_checksum(hrp, data).unwrap(); + + assert_eq!(encoded_str, written_str[..written_str.len() - CHECKSUM_LENGTH]); + } + + #[test] + fn write_with_checksum_on_drop() { let hrp = "lntb"; let data = "Hello World!".as_bytes().to_base32();