Skip to content

Commit a584931

Browse files
committed
Add public function encoded_length
Currently we do not enforce the BIP-173 maximum character length for bech32 endoded strings. Add a pubic function `encoded_length` that calculates the length of an encoding of HRP and data, returning the length either in `Ok(len)` or in an error if length exceeds 90 chars. Also add a segwit version of the function that adds 1 for the witness version. Do not call either of the functions anywhere, will be done separately. However it is unlikely that we will do length checks in the `iter` or `encode` modules (under `primitives`). Add a warning to those two modules that we do not do length checks
1 parent 202b59f commit a584931

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

src/lib.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ pub use {
160160
crate::primitives::{Bech32, Bech32m, NoChecksum},
161161
};
162162

163+
/// The maximum allowed length of a bech32 string (see [`BIP-173`]).
164+
///
165+
/// [`BIP-173`]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
166+
pub const BECH32_MAX_LENGTH: usize = 90;
167+
163168
/// Decodes a bech32 encoded string.
164169
///
165170
/// If this function succeeds the input string was found to be well formed (hrp, separator, bech32
@@ -343,6 +348,25 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
343348
Ok(())
344349
}
345350

351+
/// Returns the length of the bech32 string after encoding `hrp` and `data`.
352+
///
353+
/// # Returns
354+
///
355+
/// Returns the encoded length, ether as `Ok(len)` if valid or as `Err(EncodedLengthError(len))` if
356+
/// invalid (exceeds the maximum of 90 characters as defined in [BIP-173]).
357+
///
358+
/// [`BIP-173`]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
359+
pub fn encoded_length<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<usize, EncodedLengthError> {
360+
let iter = data.iter().copied().bytes_to_fes();
361+
let encoded_len = hrp.len() + 1 + iter.len() + Ck::CHECKSUM_LENGTH; // +1 for separator
362+
363+
if encoded_len > BECH32_MAX_LENGTH {
364+
Err(EncodedLengthError(encoded_len))
365+
} else {
366+
Ok(encoded_len)
367+
}
368+
}
369+
346370
/// An error while decoding an address.
347371
#[cfg(feature = "alloc")]
348372
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -431,6 +455,22 @@ impl From<DecodeError> for DecodeFromReaderError {
431455
fn from(e: DecodeError) -> Self { Self::Decode(e) }
432456
}
433457

458+
/// Encoding bech32 string exceeds the spec limit of 90 characters.
459+
#[derive(Debug, Clone, PartialEq, Eq)]
460+
#[non_exhaustive]
461+
pub struct EncodedLengthError(usize);
462+
463+
impl fmt::Display for EncodedLengthError {
464+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
465+
write!(f, "encoded length {} exceeds spec limit 90 chars", self.0)
466+
}
467+
}
468+
469+
#[cfg(feature = "std")]
470+
impl std::error::Error for EncodedLengthError {
471+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
472+
}
473+
434474
#[cfg(test)]
435475
#[cfg(feature = "alloc")]
436476
mod tests {

src/primitives/encode.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
//! In general, directly using these adaptors is not very ergonomic, and users are recommended to
1010
//! instead use the higher-level functions at the root of this crate.
1111
//!
12+
//! WARNING: This module does not enforce the maximum length of an encoded bech32 string (90 chars).
13+
//!
1214
//! # Examples
1315
//!
1416
//! ```

src/primitives/iter.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
//! - `FesToBytes`: An iterator over field elements to an iterator over bytes.
99
//! - `Checksummed`: An iterator over field elements that appends the checksum.
1010
//!
11+
//! WARNING: This module does not enforce the maximum length of an encoded bech32 string (90 chars).
12+
//!
1113
//! # Examples
1214
//!
1315
//! ```

src/segwit.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use crate::primitives::{Bech32, Bech32m};
5858
#[rustfmt::skip] // Keep public re-exports separate.
5959
#[doc(inline)]
6060
pub use {
61+
crate::{BECH32_MAX_LENGTH, EncodedLengthError},
6162
crate::primitives::segwit::{VERSION_0, VERSION_1},
6263
};
6364

@@ -264,6 +265,26 @@ pub fn encode_upper_to_writer_unchecked<W: std::io::Write>(
264265
Ok(())
265266
}
266267

268+
/// Returns the length of the bech32 string after encoding HRP, witness version and program.
269+
///
270+
/// # Returns
271+
///
272+
/// Returns the encoded length, ether as `Ok(len)` if valid or as `Err(EncodedLengthError(len))` if
273+
/// invalid (exceeds the maximum of 90 characters as defined in [BIP-173]).
274+
///
275+
/// [`BIP-173`]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
276+
pub fn encoded_length(
277+
hrp: &Hrp,
278+
_witness_version: Fe32, // Emphasize that this is only for segwit.
279+
witness_program: &[u8],
280+
) -> Result<usize, EncodedLengthError> {
281+
// Ck is only for length and since they are both the same we can use either here.
282+
match crate::encoded_length::<Bech32>(hrp, witness_program) {
283+
Ok(len) => Ok(len + 1), // +1 for witness version.
284+
Err(EncodedLengthError(len)) => Err(EncodedLengthError(len + 1)),
285+
}
286+
}
287+
267288
/// An error while decoding a segwit address.
268289
#[cfg(feature = "alloc")]
269290
#[derive(Debug, Clone, PartialEq, Eq)]

0 commit comments

Comments
 (0)