Skip to content

Commit b728eab

Browse files
committed
Enforce maximum string length
BIP-173 states that a bech32 string must not exceed 90 characters. However BOLT-11 states that the string limit may be exceeded. This puts us, architecturally speaking in a conundrum - we want to support lightning but this crate pretty heavily documents itself as an implementation of BIP-173 and BIP-350. The solution we choose is to enforce the string limit in the segwit modules and not in the functions in `lib.rs`. We document this decision in the crate level docs as well as on the individual functions. FTR in `bech32 v0.9.0` the lengths were not enforced either.
1 parent 323c625 commit b728eab

File tree

4 files changed

+190
-24
lines changed

4 files changed

+190
-24
lines changed

src/lib.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
//! a data part. A checksum at the end of the string provides error detection to prevent mistakes
1010
//! when the string is written off or read out loud.
1111
//!
12+
//! Please note, so as to support lighting ([BOLT-11]) we explicitly do not do string length checks
13+
//! in the top level API. We do however enforce the 90 character limit within the `segwit` modules.
14+
//!
1215
//! # Usage
1316
//!
1417
//! - If you are doing segwit stuff you likely want to use the [`segwit`] API.
@@ -113,6 +116,8 @@
113116
//!
114117
//! # }
115118
//! ```
119+
//!
120+
//! [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
116121
117122
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
118123
// Experimental features we need.
@@ -166,7 +171,10 @@ pub use {
166171
/// If this function succeeds the input string was found to be well formed (hrp, separator, bech32
167172
/// characters), and to have either a valid bech32m checksum or a valid bech32 checksum.
168173
///
169-
/// If your input string has no checksum use the [`CheckedHrpstring`] constructor, which allows selecting the checksum algorithm explicitly.
174+
/// If your input string has no checksum use the [`CheckedHrpstring`] constructor, which allows
175+
/// selecting the checksum algorithm explicitly.
176+
///
177+
/// Note: this function does not enforce any restrictions on the total length of the input string.
170178
///
171179
/// # Returns
172180
///
@@ -213,6 +221,15 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
213221
///
214222
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
215223
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
224+
///
225+
/// ## Deviation from spec (BIP-173)
226+
///
227+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
228+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
229+
///
230+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
231+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
232+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
216233
#[cfg(feature = "alloc")]
217234
#[inline]
218235
pub fn encode<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt::Error> {
@@ -223,6 +240,15 @@ pub fn encode<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt::Error
223240
///
224241
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
225242
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
243+
///
244+
/// ## Deviation from spec (BIP-173)
245+
///
246+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
247+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
248+
///
249+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
250+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
251+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
226252
#[cfg(feature = "alloc")]
227253
#[inline]
228254
pub fn encode_lower<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt::Error> {
@@ -235,6 +261,15 @@ pub fn encode_lower<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt:
235261
///
236262
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
237263
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
264+
///
265+
/// ## Deviation from spec (BIP-173)
266+
///
267+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
268+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
269+
///
270+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
271+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
272+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
238273
#[cfg(feature = "alloc")]
239274
#[inline]
240275
pub fn encode_upper<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt::Error> {
@@ -247,6 +282,15 @@ pub fn encode_upper<Ck: Checksum>(hrp: &Hrp, data: &[u8]) -> Result<String, fmt:
247282
///
248283
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
249284
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
285+
///
286+
/// ## Deviation from spec (BIP-173)
287+
///
288+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
289+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
290+
///
291+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
292+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
293+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
250294
#[inline]
251295
pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
252296
fmt: &mut W,
@@ -260,6 +304,15 @@ pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
260304
///
261305
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
262306
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
307+
///
308+
/// ## Deviation from spec (BIP-173)
309+
///
310+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
311+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
312+
///
313+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
314+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
315+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
263316
#[inline]
264317
pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
265318
fmt: &mut W,
@@ -278,6 +331,15 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
278331
///
279332
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
280333
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
334+
///
335+
/// ## Deviation from spec (BIP-173)
336+
///
337+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
338+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
339+
///
340+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
341+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
342+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
281343
#[inline]
282344
pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
283345
fmt: &mut W,
@@ -296,6 +358,15 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
296358
///
297359
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
298360
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
361+
///
362+
/// ## Deviation from spec (BIP-173)
363+
///
364+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
365+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
366+
///
367+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
368+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
369+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
299370
#[cfg(feature = "std")]
300371
#[inline]
301372
pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
@@ -310,6 +381,15 @@ pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
310381
///
311382
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
312383
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
384+
///
385+
/// ## Deviation from spec (BIP-173)
386+
///
387+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
388+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
389+
///
390+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
391+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
392+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
313393
#[cfg(feature = "std")]
314394
#[inline]
315395
pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
@@ -329,6 +409,15 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
329409
///
330410
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
331411
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
412+
///
413+
/// ## Deviation from spec (BIP-173)
414+
///
415+
/// In order to support [BOLT-11] this function does not restrict the total length of the returned
416+
/// string. To encode [BIP-173] / [BIP-350] compliant segwit addresses use [`segwit::encode`].
417+
///
418+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
419+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
420+
/// [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
332421
#[cfg(feature = "std")]
333422
#[inline]
334423
pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
@@ -492,4 +581,22 @@ mod tests {
492581

493582
assert_eq!(got, want);
494583
}
584+
585+
#[test]
586+
fn can_encode_really_long_string() {
587+
// Encode around the bech32 limit, mainly here as documentation.
588+
let tcs = vec![
589+
// Also shows there are no limit checks on the data slice (since segwit limits this to 40 bytes).
590+
([0_u8; 50], Hrp::parse_unchecked("abc")), // Encodes to 90 characters.
591+
([0_u8; 50], Hrp::parse_unchecked("abcd")), // Encodes to 91 characters.
592+
];
593+
for (data, hrp) in tcs {
594+
assert!(encode::<Bech32>(&hrp, &data).is_ok());
595+
}
596+
597+
// Encode something arbitrarily long.
598+
let data = [0_u8; 1024];
599+
let hrp = Hrp::parse_unchecked("abc");
600+
assert!(encode::<Bech32m>(&hrp, &data).is_ok());
601+
}
495602
}

src/primitives/decode.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ use crate::primitives::gf32::Fe32;
8181
use crate::primitives::hrp::{self, Hrp};
8282
use crate::primitives::iter::{Fe32IterExt, FesToBytes};
8383
use crate::primitives::segwit::{self, WitnessLengthError, VERSION_0};
84+
use crate::primitives::MAX_STRING_LENGTH;
8485
use crate::{Bech32, Bech32m};
8586

8687
/// Separator between the hrp and payload (as defined by BIP-173).
@@ -127,6 +128,11 @@ impl<'s> UncheckedHrpstring<'s> {
127128
/// Checks for valid ASCII values, does not validate the checksum.
128129
#[inline]
129130
pub fn new(s: &'s str) -> Result<Self, UncheckedHrpstringError> {
131+
let len = s.len();
132+
if len > MAX_STRING_LENGTH {
133+
return Err(UncheckedHrpstringError::TooLong(len));
134+
}
135+
130136
let sep_pos = check_characters(s)?;
131137
let (hrp, data) = s.split_at(sep_pos);
132138

@@ -658,6 +664,8 @@ impl From<ChecksumError> for CheckedHrpstringError {
658664
#[derive(Debug, Clone, PartialEq, Eq)]
659665
#[non_exhaustive]
660666
pub enum UncheckedHrpstringError {
667+
/// String exceeds maximum allowed length.
668+
TooLong(usize),
661669
/// An error with the characters of the input string.
662670
Char(CharError),
663671
/// The human-readable part is invalid.
@@ -669,6 +677,8 @@ impl fmt::Display for UncheckedHrpstringError {
669677
use UncheckedHrpstringError::*;
670678

671679
match *self {
680+
TooLong(len) =>
681+
write!(f, "string exceeds spec limit of {} chars: {}", MAX_STRING_LENGTH, len),
672682
Char(ref e) => write_err!(f, "character error"; e),
673683
Hrp(ref e) => write_err!(f, "invalid human-readable part"; e),
674684
}
@@ -681,6 +691,7 @@ impl std::error::Error for UncheckedHrpstringError {
681691
use UncheckedHrpstringError::*;
682692

683693
match *self {
694+
TooLong(_) => None,
684695
Char(ref e) => Some(e),
685696
Hrp(ref e) => Some(e),
686697
}
@@ -827,7 +838,7 @@ mod tests {
827838
("\u{80}1eym55h",
828839
Hrp(hrp::Error::NonAsciiChar('\u{80}'))),
829840
("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
830-
Hrp(hrp::Error::TooLong(84))),
841+
TooLong(91)),
831842
("pzry9x0s0muk",
832843
Char(CharError::MissingSeparator)),
833844
("1pzry9x0s0muk",
@@ -880,7 +891,7 @@ mod tests {
880891
("\u{80}1g6xzxy",
881892
Hrp(hrp::Error::NonAsciiChar('\u{80}'))),
882893
("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
883-
Hrp(hrp::Error::TooLong(84))),
894+
TooLong(91)),
884895
("qyrz8wqd2c9m",
885896
Char(CharError::MissingSeparator)),
886897
("1qyrz8wqd2c9m",

src/primitives/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ pub mod segwit;
1212

1313
use checksum::{Checksum, PackedNull};
1414

15+
/// The maximum allowed length of a bech32 string (see [`BIP-173`]).
16+
///
17+
/// [`BIP-173`]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
18+
pub const MAX_STRING_LENGTH: usize = 90;
19+
1520
/// The "null checksum" used on bech32 strings for which we want to do no checksum checking.
1621
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1722
pub enum NoChecksum {}

0 commit comments

Comments
 (0)