2323//! The original description in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
2424//! has more details. See also [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki).
2525//!
26+ //! # Deviation from spec
27+ //!
28+ //! We do not enforce the 90 character limit specified by [BIP-173], instead we enforce the code
29+ //! length for the respective checksum algorithm (see [`Checksum::CODE_LENGTH`]). We do however
30+ //! enforce the 90 character limit within the `segwit` modules and types.
31+ //!
2632//! # Examples
2733//!
2834//! ## Encoding
100106//! impl Checksum for Codex32 {
101107//! type MidstateRepr = u128;
102108//! const CHECKSUM_LENGTH: usize = 13;
109+ //! const CODE_LENGTH: usize = 93;
103110//! // Copied from BIP-93
104111//! const GENERATOR_SH: [u128; 5] = [
105112//! 0x19dc500ce73fde210,
113120//!
114121//! # }
115122//! ```
123+ //!
124+ //! [`Checksum::CODE_LENGTH`]: crate::primitives::checksum::Checksum::CODE_LENGTH
116125
117126#![ cfg_attr( all( not( feature = "std" ) , not( test) ) , no_std) ]
118127// Experimental features we need.
@@ -142,14 +151,12 @@ pub mod segwit;
142151use alloc:: { string:: String , vec:: Vec } ;
143152use core:: fmt;
144153
145- #[ cfg( feature = "alloc" ) ]
146154use crate :: error:: write_err;
147155#[ cfg( doc) ]
148156use crate :: primitives:: decode:: CheckedHrpstring ;
157+ use crate :: primitives:: decode:: CodeLengthError ;
149158#[ cfg( feature = "alloc" ) ]
150- use crate :: primitives:: decode:: UncheckedHrpstringError ;
151- #[ cfg( feature = "alloc" ) ]
152- use crate :: primitives:: decode:: { ChecksumError , UncheckedHrpstring } ;
159+ use crate :: primitives:: decode:: { ChecksumError , UncheckedHrpstring , UncheckedHrpstringError } ;
153160
154161#[ rustfmt:: skip] // Keep public re-exports separate.
155162#[ doc( inline) ]
@@ -216,7 +223,7 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
216223/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
217224#[ cfg( feature = "alloc" ) ]
218225#[ inline]
219- pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
226+ pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
220227 encode_lower :: < Ck > ( hrp, data)
221228}
222229
@@ -226,7 +233,7 @@ pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error>
226233/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
227234#[ cfg( feature = "alloc" ) ]
228235#[ inline]
229- pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
236+ pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
230237 let mut buf = String :: new ( ) ;
231238 encode_lower_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
232239 Ok ( buf)
@@ -238,7 +245,7 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
238245/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
239246#[ cfg( feature = "alloc" ) ]
240247#[ inline]
241- pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
248+ pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
242249 let mut buf = String :: new ( ) ;
243250 encode_upper_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
244251 Ok ( buf)
@@ -253,7 +260,7 @@ pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
253260 fmt : & mut W ,
254261 hrp : Hrp ,
255262 data : & [ u8 ] ,
256- ) -> Result < ( ) , fmt :: Error > {
263+ ) -> Result < ( ) , EncodeError > {
257264 encode_lower_to_fmt :: < Ck , W > ( fmt, hrp, data)
258265}
259266
@@ -266,7 +273,9 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
266273 fmt : & mut W ,
267274 hrp : Hrp ,
268275 data : & [ u8 ] ,
269- ) -> Result < ( ) , fmt:: Error > {
276+ ) -> Result < ( ) , EncodeError > {
277+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
278+
270279 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
271280 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
272281 for c in chars {
@@ -284,7 +293,9 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
284293 fmt : & mut W ,
285294 hrp : Hrp ,
286295 data : & [ u8 ] ,
287- ) -> Result < ( ) , fmt:: Error > {
296+ ) -> Result < ( ) , EncodeError > {
297+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
298+
288299 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
289300 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
290301 for c in chars {
@@ -303,7 +314,7 @@ pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
303314 w : & mut W ,
304315 hrp : Hrp ,
305316 data : & [ u8 ] ,
306- ) -> Result < ( ) , std :: io :: Error > {
317+ ) -> Result < ( ) , EncodeIoError > {
307318 encode_lower_to_writer :: < Ck , W > ( w, hrp, data)
308319}
309320
@@ -317,7 +328,9 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
317328 w : & mut W ,
318329 hrp : Hrp ,
319330 data : & [ u8 ] ,
320- ) -> Result < ( ) , std:: io:: Error > {
331+ ) -> Result < ( ) , EncodeIoError > {
332+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
333+
321334 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
322335 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
323336 for c in chars {
@@ -336,7 +349,9 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
336349 w : & mut W ,
337350 hrp : Hrp ,
338351 data : & [ u8 ] ,
339- ) -> Result < ( ) , std:: io:: Error > {
352+ ) -> Result < ( ) , EncodeIoError > {
353+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
354+
340355 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
341356 let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
342357 for c in chars {
@@ -345,10 +360,23 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
345360 Ok ( ( ) )
346361}
347362
348- /// Returns the length of the bech32 string after encoding `hrp` and `data` (incl. checksum).
349- pub fn encoded_length < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> usize {
363+ /// Checks that encoding `hrp` and `data` creates a code this is less that the code length for `Ck`.
364+ ///
365+ /// The length of the code is how long a coded message can be (including the checksum!) for the code
366+ /// to retain its error-correcting properties.
367+ ///
368+ /// # Returns
369+ ///
370+ /// `Ok(encoded_string_length)` if the encoded length is less than or equal to `Ck::CODE_LENGTH`
371+ /// otherwise a [`CodeLengthError`] containing the encoded length and the maximum allowed.
372+ pub fn encoded_length < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < usize , CodeLengthError > {
350373 let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
351- hrp. len ( ) + 1 + iter. len ( ) + Ck :: CHECKSUM_LENGTH // +1 for separator
374+ let len = hrp. len ( ) + 1 + iter. len ( ) + Ck :: CHECKSUM_LENGTH ; // +1 for separator
375+ if len > Ck :: CODE_LENGTH {
376+ Err ( CodeLengthError { encoded_length : len, code_length : Ck :: CODE_LENGTH } )
377+ } else {
378+ Ok ( len)
379+ }
352380}
353381
354382/// An error while decoding a bech32 string.
@@ -392,6 +420,96 @@ impl From<UncheckedHrpstringError> for DecodeError {
392420 fn from ( e : UncheckedHrpstringError ) -> Self { Self :: Parse ( e) }
393421}
394422
423+ /// An error while encoding a bech32 string.
424+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
425+ #[ non_exhaustive]
426+ pub enum EncodeError {
427+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
428+ TooLong ( CodeLengthError ) ,
429+ /// Encode to formatter failed.
430+ Fmt ( fmt:: Error ) ,
431+ }
432+
433+ impl fmt:: Display for EncodeError {
434+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
435+ use EncodeError :: * ;
436+
437+ match * self {
438+ TooLong ( ref e) => write_err ! ( f, "encode error" ; e) ,
439+ Fmt ( ref e) => write_err ! ( f, "encode to formatter failed" ; e) ,
440+ }
441+ }
442+ }
443+
444+ #[ cfg( feature = "std" ) ]
445+ impl std:: error:: Error for EncodeError {
446+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
447+ use EncodeError :: * ;
448+
449+ match * self {
450+ TooLong ( ref e) => Some ( e) ,
451+ Fmt ( ref e) => Some ( e) ,
452+ }
453+ }
454+ }
455+
456+ impl From < CodeLengthError > for EncodeError {
457+ #[ inline]
458+ fn from ( e : CodeLengthError ) -> Self { Self :: TooLong ( e) }
459+ }
460+
461+ impl From < fmt:: Error > for EncodeError {
462+ #[ inline]
463+ fn from ( e : fmt:: Error ) -> Self { Self :: Fmt ( e) }
464+ }
465+
466+ /// An error while encoding a bech32 string.
467+ #[ cfg( feature = "std" ) ]
468+ #[ derive( Debug ) ]
469+ #[ non_exhaustive]
470+ pub enum EncodeIoError {
471+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
472+ TooLong ( CodeLengthError ) ,
473+ /// Encode to writer failed.
474+ Write ( std:: io:: Error ) ,
475+ }
476+
477+ #[ cfg( feature = "std" ) ]
478+ impl fmt:: Display for EncodeIoError {
479+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
480+ use EncodeIoError :: * ;
481+
482+ match * self {
483+ TooLong ( ref e) => write_err ! ( f, "encode error" ; e) ,
484+ Write ( ref e) => write_err ! ( f, "encode to writer failed" ; e) ,
485+ }
486+ }
487+ }
488+
489+ #[ cfg( feature = "std" ) ]
490+ impl std:: error:: Error for EncodeIoError {
491+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
492+ use EncodeIoError :: * ;
493+
494+ match * self {
495+ TooLong ( ref e) => Some ( e) ,
496+ Write ( ref e) => Some ( e) ,
497+ }
498+ }
499+ }
500+
501+ #[ cfg( feature = "std" ) ]
502+ impl From < CodeLengthError > for EncodeIoError {
503+ #[ inline]
504+ fn from ( e : CodeLengthError ) -> Self { Self :: TooLong ( e) }
505+ }
506+
507+ #[ cfg( feature = "std" ) ]
508+ impl From < std:: io:: Error > for EncodeIoError {
509+ #[ inline]
510+ fn from ( e : std:: io:: Error ) -> Self { Self :: Write ( e) }
511+ }
512+
395513#[ cfg( test) ]
396514#[ cfg( feature = "alloc" ) ]
397515mod tests {
@@ -489,8 +607,36 @@ mod tests {
489607
490608 let encoded = encode :: < Bech32m > ( hrp, & data) . expect ( "valid data" ) ;
491609 let want = encoded. len ( ) ;
492- let got = encoded_length :: < Bech32m > ( hrp, & data) ;
610+ let got = encoded_length :: < Bech32m > ( hrp, & data) . expect ( "encoded length" ) ;
493611
494612 assert_eq ! ( got, want) ;
495613 }
614+
615+ #[ test]
616+ fn can_encode_maximum_length_string ( ) {
617+ let data = [ 0_u8 ; 632 ] ;
618+ let hrp = Hrp :: parse_unchecked ( "abcd" ) ;
619+ let s = encode :: < Bech32m > ( hrp, & data) . expect ( "valid data" ) ;
620+ assert_eq ! ( s. len( ) , 1023 ) ;
621+ }
622+
623+ #[ test]
624+ fn can_not_encode_string_too_long ( ) {
625+ let data = [ 0_u8 ; 632 ] ;
626+ let hrp = Hrp :: parse_unchecked ( "abcde" ) ;
627+
628+ match encode :: < Bech32m > ( hrp, & data) {
629+ Ok ( _) => panic ! ( "false positive" ) ,
630+ Err ( EncodeError :: TooLong ( CodeLengthError { encoded_length, code_length : _ } ) ) =>
631+ assert_eq ! ( encoded_length, 1024 ) ,
632+ _ => panic ! ( "false negative" ) ,
633+ }
634+ }
635+
636+ #[ test]
637+ fn can_decode_segwit_too_long_string ( ) {
638+ // A 91 character long string, greater than the segwit enforced maximum of 90.
639+ let s = "abcd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrw9z3s" ;
640+ assert ! ( decode( s) . is_ok( ) ) ;
641+ }
496642}
0 commit comments