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, in order to support lighting ([BOLT-11]) we do not enforce the 90 character limit
13+ //! specified by [BIP-173], instead we use 1023 because that is a property of the `Bech32` and
14+ //! `Bech32m` checksum algorithms (specifically error detection, see the [`checksum`] module
15+ //! documentation for more information). We do however enforce the 90 character limit within the
16+ //! `segwit` modules.
17+ //!
1218//! # Usage
1319//!
1420//! - If you are doing segwit stuff you likely want to use the [`segwit`] API.
8995//!
9096//! ## Custom Checksum
9197//!
98+ //! Please note, if your checksum algorithm can detect errors in data greater than 1023 characters,
99+ //! and you intend on leveraging this fact, then this crate will not currently serve your needs.
100+ //! Patches welcome.
101+ //!
92102//! ```
93103//! # #[cfg(feature = "alloc")] {
94104//! use bech32::Checksum;
113123//!
114124//! # }
115125//! ```
126+ //!
127+ //! [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
128+ //! [`checksum`]: crate::primitives::checksum
116129
117130#![ cfg_attr( all( not( feature = "std" ) , not( test) ) , no_std) ]
118131// Experimental features we need.
@@ -144,6 +157,7 @@ use core::fmt;
144157
145158#[ cfg( feature = "alloc" ) ]
146159use crate :: error:: write_err;
160+ use crate :: primitives:: checksum:: MAX_STRING_LENGTH ;
147161#[ cfg( doc) ]
148162use crate :: primitives:: decode:: CheckedHrpstring ;
149163#[ cfg( feature = "alloc" ) ]
@@ -214,19 +228,32 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
214228///
215229/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
216230/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
231+ ///
232+ /// ## Deviation from spec (BIP-173)
233+ ///
234+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
217235#[ cfg( feature = "alloc" ) ]
218236#[ inline]
219- pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt:: Error > {
237+ pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
238+ let encoded_length = encoded_length :: < Ck > ( hrp, data) ;
239+ if encoded_length > MAX_STRING_LENGTH {
240+ return Err ( EncodeError :: TooLong ( encoded_length) ) ;
241+ }
242+
220243 encode_lower :: < Ck > ( hrp, data)
221244}
222245
223246/// Encodes `data` as a lowercase bech32 encoded string.
224247///
225248/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
226249/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
250+ ///
251+ /// ## Deviation from spec (BIP-173)
252+ ///
253+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
227254#[ cfg( feature = "alloc" ) ]
228255#[ inline]
229- pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
256+ pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
230257 let mut buf = String :: new ( ) ;
231258 encode_lower_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
232259 Ok ( buf)
@@ -236,9 +263,13 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
236263///
237264/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
238265/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
266+ ///
267+ /// ## Deviation from spec (BIP-173)
268+ ///
269+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
239270#[ cfg( feature = "alloc" ) ]
240271#[ inline]
241- pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
272+ pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
242273 let mut buf = String :: new ( ) ;
243274 encode_upper_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
244275 Ok ( buf)
@@ -248,25 +279,33 @@ pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
248279///
249280/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
250281/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
282+ ///
283+ /// ## Deviation from spec (BIP-173)
284+ ///
285+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
251286#[ inline]
252287pub fn encode_to_fmt < Ck : Checksum , W : fmt:: Write > (
253288 fmt : & mut W ,
254289 hrp : Hrp ,
255290 data : & [ u8 ] ,
256- ) -> Result < ( ) , fmt :: Error > {
291+ ) -> Result < ( ) , EncodeError > {
257292 encode_lower_to_fmt :: < Ck , W > ( fmt, hrp, data)
258293}
259294
260295/// Encodes `data` to a writer ([`fmt::Write`]) as a lowercase bech32 encoded string.
261296///
262297/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
263298/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
299+ ///
300+ /// ## Deviation from spec (BIP-173)
301+ ///
302+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
264303#[ inline]
265304pub fn encode_lower_to_fmt < Ck : Checksum , W : fmt:: Write > (
266305 fmt : & mut W ,
267306 hrp : Hrp ,
268307 data : & [ u8 ] ,
269- ) -> Result < ( ) , fmt :: Error > {
308+ ) -> Result < ( ) , EncodeError > {
270309 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
271310 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
272311 for c in chars {
@@ -279,12 +318,16 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
279318///
280319/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
281320/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
321+ ///
322+ /// ## Deviation from spec (BIP-173)
323+ ///
324+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
282325#[ inline]
283326pub fn encode_upper_to_fmt < Ck : Checksum , W : fmt:: Write > (
284327 fmt : & mut W ,
285328 hrp : Hrp ,
286329 data : & [ u8 ] ,
287- ) -> Result < ( ) , fmt :: Error > {
330+ ) -> Result < ( ) , EncodeError > {
288331 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
289332 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
290333 for c in chars {
@@ -297,27 +340,35 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
297340///
298341/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
299342/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
343+ ///
344+ /// ## Deviation from spec (BIP-173)
345+ ///
346+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
300347#[ cfg( feature = "std" ) ]
301348#[ inline]
302349pub fn encode_to_writer < Ck : Checksum , W : std:: io:: Write > (
303350 w : & mut W ,
304351 hrp : Hrp ,
305352 data : & [ u8 ] ,
306- ) -> Result < ( ) , std :: io :: Error > {
353+ ) -> Result < ( ) , EncodeIoError > {
307354 encode_lower_to_writer :: < Ck , W > ( w, hrp, data)
308355}
309356
310357/// Encodes `data` to a writer ([`std::io::Write`]) as a lowercase bech32 encoded string.
311358///
312359/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
313360/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
361+ ///
362+ /// ## Deviation from spec (BIP-173)
363+ ///
364+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
314365#[ cfg( feature = "std" ) ]
315366#[ inline]
316367pub fn encode_lower_to_writer < Ck : Checksum , W : std:: io:: Write > (
317368 w : & mut W ,
318369 hrp : Hrp ,
319370 data : & [ u8 ] ,
320- ) -> Result < ( ) , std :: io :: Error > {
371+ ) -> Result < ( ) , EncodeIoError > {
321372 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
322373 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
323374 for c in chars {
@@ -330,13 +381,17 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
330381///
331382/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
332383/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
384+ ///
385+ /// ## Deviation from spec (BIP-173)
386+ ///
387+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
333388#[ cfg( feature = "std" ) ]
334389#[ inline]
335390pub fn encode_upper_to_writer < Ck : Checksum , W : std:: io:: Write > (
336391 w : & mut W ,
337392 hrp : Hrp ,
338393 data : & [ u8 ] ,
339- ) -> Result < ( ) , std :: io :: Error > {
394+ ) -> Result < ( ) , EncodeIoError > {
340395 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
341396 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
342397 for c in chars {
@@ -392,6 +447,88 @@ impl From<UncheckedHrpstringError> for DecodeError {
392447 fn from ( e : UncheckedHrpstringError ) -> Self { Self :: Parse ( e) }
393448}
394449
450+ /// An error while encoding a bech32 string.
451+ #[ cfg( feature = "alloc" ) ]
452+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
453+ #[ non_exhaustive]
454+ pub enum EncodeError {
455+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
456+ TooLong ( usize ) ,
457+ /// Encode to formatter failed.
458+ Fmt ( fmt:: Error ) ,
459+ }
460+
461+ impl fmt:: Display for EncodeError {
462+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
463+ use EncodeError :: * ;
464+
465+ match * self {
466+ TooLong ( len) =>
467+ write ! ( f, "encoded length {} exceeds spec limit {} chars" , len, MAX_STRING_LENGTH ) ,
468+ Fmt ( ref e) => write_err ! ( f, "encode to formatter failed" ; e) ,
469+ }
470+ }
471+ }
472+
473+ #[ cfg( feature = "std" ) ]
474+ impl std:: error:: Error for EncodeError {
475+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
476+ use EncodeError :: * ;
477+
478+ match * self {
479+ TooLong ( _) => None ,
480+ Fmt ( ref e) => Some ( e) ,
481+ }
482+ }
483+ }
484+
485+ impl From < fmt:: Error > for EncodeError {
486+ #[ inline]
487+ fn from ( e : fmt:: Error ) -> Self { Self :: Fmt ( e) }
488+ }
489+
490+ /// An error while encoding a bech32 string.
491+ #[ cfg( feature = "std" ) ]
492+ #[ derive( Debug ) ]
493+ #[ non_exhaustive]
494+ pub enum EncodeIoError {
495+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
496+ TooLong ( usize ) ,
497+ /// Encode to writer failed.
498+ Write ( std:: io:: Error ) ,
499+ }
500+
501+ #[ cfg( feature = "std" ) ]
502+ impl fmt:: Display for EncodeIoError {
503+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
504+ use EncodeIoError :: * ;
505+
506+ match * self {
507+ TooLong ( len) =>
508+ write ! ( f, "encoded length {} exceeds spec limit {} chars" , len, MAX_STRING_LENGTH ) ,
509+ Write ( ref e) => write_err ! ( f, "encode to writer failed" ; e) ,
510+ }
511+ }
512+ }
513+
514+ #[ cfg( feature = "std" ) ]
515+ impl std:: error:: Error for EncodeIoError {
516+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
517+ use EncodeIoError :: * ;
518+
519+ match * self {
520+ TooLong ( _) => None ,
521+ Write ( ref e) => Some ( e) ,
522+ }
523+ }
524+ }
525+
526+ #[ cfg( feature = "std" ) ]
527+ impl From < std:: io:: Error > for EncodeIoError {
528+ #[ inline]
529+ fn from ( e : std:: io:: Error ) -> Self { Self :: Write ( e) }
530+ }
531+
395532#[ cfg( test) ]
396533#[ cfg( feature = "alloc" ) ]
397534mod tests {
@@ -493,4 +630,31 @@ mod tests {
493630
494631 assert_eq ! ( got, want) ;
495632 }
633+
634+ #[ test]
635+ fn can_encode_maximum_length_string ( ) {
636+ let data = [ 0_u8 ; 632 ] ;
637+ let hrp = Hrp :: parse_unchecked ( "abcd" ) ;
638+ let s = encode :: < Bech32m > ( hrp, & data) . expect ( "failed to encode string" ) ;
639+ assert_eq ! ( s. len( ) , 1023 ) ;
640+ }
641+
642+ #[ test]
643+ fn can_not_encode_string_too_long ( ) {
644+ let data = [ 0_u8 ; 632 ] ;
645+ let hrp = Hrp :: parse_unchecked ( "abcde" ) ;
646+
647+ match encode :: < Bech32m > ( hrp, & data) {
648+ Ok ( _) => panic ! ( "false positive" ) ,
649+ Err ( EncodeError :: TooLong ( len) ) => assert_eq ! ( len, 1024 ) ,
650+ _ => panic ! ( "false negative" ) ,
651+ }
652+ }
653+
654+ #[ test]
655+ fn can_decode_segwit_too_long_string ( ) {
656+ // A 91 character long string, greater than the segwit enforced maximum of 90.
657+ let s = "abcd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrw9z3s" ;
658+ assert ! ( decode( s) . is_ok( ) ) ;
659+ }
496660}
0 commit comments