Skip to content

Commit d09c7b1

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 d09c7b1

File tree

4 files changed

+66
-0
lines changed

4 files changed

+66
-0
lines changed

src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub mod segwit;
142142
use alloc::{string::String, vec::Vec};
143143
use core::fmt;
144144

145+
#[cfg(feature = "alloc")]
145146
use crate::error::write_err;
146147
#[cfg(doc)]
147148
use crate::primitives::decode::CheckedHrpstring;
@@ -160,6 +161,11 @@ pub use {
160161
crate::primitives::{Bech32, Bech32m, NoChecksum},
161162
};
162163

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

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

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