diff --git a/src/lib.rs b/src/lib.rs index e829e0f76..ccc98d587 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -344,6 +344,12 @@ pub fn encode_upper_to_writer( Ok(()) } +/// Returns the length of the bech32 string after encoding `hrp` and `data` (incl. checksum). +pub fn encoded_length(hrp: &Hrp, data: &[u8]) -> usize { + let iter = data.iter().copied().bytes_to_fes(); + hrp.len() + 1 + iter.len() + Ck::CHECKSUM_LENGTH // +1 for separator +} + /// An error while decoding an address. #[cfg(feature = "alloc")] #[derive(Debug, Clone, PartialEq, Eq)] @@ -474,4 +480,16 @@ mod tests { 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("failed to decode valid address"); + + let encoded = encode::(&hrp, &data).expect("failed to encode string"); + let want = encoded.len(); + let got = encoded_length::(&hrp, &data); + + assert_eq!(got, want); + } } diff --git a/src/primitives/encode.rs b/src/primitives/encode.rs index b7d1fcd9b..93e8a92d0 100644 --- a/src/primitives/encode.rs +++ b/src/primitives/encode.rs @@ -9,6 +9,8 @@ //! In general, directly using these adaptors is not very ergonomic, and users are recommended to //! instead use the higher-level functions at the root of this crate. //! +//! WARNING: This module does not enforce the maximum length of an encoded bech32 string (90 chars). +//! //! # Examples //! //! ``` diff --git a/src/primitives/iter.rs b/src/primitives/iter.rs index c58904dc4..5b19c4ddc 100644 --- a/src/primitives/iter.rs +++ b/src/primitives/iter.rs @@ -8,6 +8,8 @@ //! - `FesToBytes`: An iterator over field elements to an iterator over bytes. //! - `Checksummed`: An iterator over field elements that appends the checksum. //! +//! WARNING: This module does not enforce the maximum length of an encoded bech32 string (90 chars). +//! //! # Examples //! //! ``` diff --git a/src/segwit.rs b/src/segwit.rs index 80e258a42..17a2598bf 100644 --- a/src/segwit.rs +++ b/src/segwit.rs @@ -264,6 +264,23 @@ pub fn encode_upper_to_writer_unchecked( Ok(()) } +/// Returns the length of the bech32 string after encoding HRP, witness version and program. +/// +/// # Returns +/// +/// Returns the encoded length, ether as `Ok(len)` if valid or as `Err(EncodedLengthError(len))` if +/// invalid (exceeds the maximum of 90 characters as defined in [BIP-173]). +/// +/// [`BIP-173`]: +pub fn encoded_length( + hrp: &Hrp, + _witness_version: Fe32, // Emphasize that this is only for segwit. + witness_program: &[u8], +) -> usize { + // Ck is only for length and since they are both the same we can use either here. + crate::encoded_length::(hrp, witness_program) + 1 // +1 for witness version. +} + /// An error while decoding a segwit address. #[cfg(feature = "alloc")] #[derive(Debug, Clone, PartialEq, Eq)] @@ -416,4 +433,22 @@ mod tests { let want = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"; assert_eq!(address, want); } + + #[test] + fn encoded_length_works() { + let addresses = vec![ + "bc1q2s3rjwvam9dt2ftt4sqxqjf3twav0gdx0k0q2etxflx38c3x8tnssdmnjq", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", + ]; + + for address in addresses { + let (hrp, version, program) = decode(address).expect("failed to decode valid address"); + + let encoded = encode(&hrp, version, &program).expect("failed to encode string"); + let want = encoded.len(); + let got = encoded_length(&hrp, version, &program); + + assert_eq!(got, want); + } + } }