9
9
//! a data part. A checksum at the end of the string provides error detection to prevent mistakes
10
10
//! when the string is written off or read out loud.
11
11
//!
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
+ //!
12
18
//! # Usage
13
19
//!
14
20
//! - If you are doing segwit stuff you likely want to use the [`segwit`] API.
89
95
//!
90
96
//! ## Custom Checksum
91
97
//!
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
+ //!
92
102
//! ```
93
103
//! # #[cfg(feature = "alloc")] {
94
104
//! use bech32::Checksum;
113
123
//!
114
124
//! # }
115
125
//! ```
126
+ //!
127
+ //! [BOLT-11]: <https://github.com/lightning/bolts/blob/master/11-payment-encoding.md>
128
+ //! [`checksum`]: crate::primitives::checksum
116
129
117
130
#![ cfg_attr( all( not( feature = "std" ) , not( test) ) , no_std) ]
118
131
// Experimental features we need.
@@ -142,8 +155,8 @@ pub mod segwit;
142
155
use alloc:: { string:: String , vec:: Vec } ;
143
156
use core:: fmt;
144
157
145
- #[ cfg( feature = "alloc" ) ]
146
158
use crate :: error:: write_err;
159
+ use crate :: primitives:: checksum:: MAX_STRING_LENGTH ;
147
160
#[ cfg( doc) ]
148
161
use crate :: primitives:: decode:: CheckedHrpstring ;
149
162
#[ cfg( feature = "alloc" ) ]
@@ -214,19 +227,32 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
214
227
///
215
228
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
216
229
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
230
+ ///
231
+ /// ## Deviation from spec (BIP-173)
232
+ ///
233
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
217
234
#[ cfg( feature = "alloc" ) ]
218
235
#[ inline]
219
- pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt:: Error > {
236
+ pub fn encode < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
237
+ let encoded_length = encoded_length :: < Ck > ( hrp, data) ;
238
+ if encoded_length > MAX_STRING_LENGTH {
239
+ return Err ( EncodeError :: TooLong ( encoded_length) ) ;
240
+ }
241
+
220
242
encode_lower :: < Ck > ( hrp, data)
221
243
}
222
244
223
245
/// Encodes `data` as a lowercase bech32 encoded string.
224
246
///
225
247
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
226
248
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
249
+ ///
250
+ /// ## Deviation from spec (BIP-173)
251
+ ///
252
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
227
253
#[ cfg( feature = "alloc" ) ]
228
254
#[ inline]
229
- pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
255
+ pub fn encode_lower < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
230
256
let mut buf = String :: new ( ) ;
231
257
encode_lower_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
232
258
Ok ( buf)
@@ -236,9 +262,13 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
236
262
///
237
263
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
238
264
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
265
+ ///
266
+ /// ## Deviation from spec (BIP-173)
267
+ ///
268
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
239
269
#[ cfg( feature = "alloc" ) ]
240
270
#[ inline]
241
- pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , fmt :: Error > {
271
+ pub fn encode_upper < Ck : Checksum > ( hrp : Hrp , data : & [ u8 ] ) -> Result < String , EncodeError > {
242
272
let mut buf = String :: new ( ) ;
243
273
encode_upper_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
244
274
Ok ( buf)
@@ -248,25 +278,33 @@ pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
248
278
///
249
279
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
250
280
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
281
+ ///
282
+ /// ## Deviation from spec (BIP-173)
283
+ ///
284
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
251
285
#[ inline]
252
286
pub fn encode_to_fmt < Ck : Checksum , W : fmt:: Write > (
253
287
fmt : & mut W ,
254
288
hrp : Hrp ,
255
289
data : & [ u8 ] ,
256
- ) -> Result < ( ) , fmt :: Error > {
290
+ ) -> Result < ( ) , EncodeError > {
257
291
encode_lower_to_fmt :: < Ck , W > ( fmt, hrp, data)
258
292
}
259
293
260
294
/// Encodes `data` to a writer ([`fmt::Write`]) as a lowercase bech32 encoded string.
261
295
///
262
296
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
263
297
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
298
+ ///
299
+ /// ## Deviation from spec (BIP-173)
300
+ ///
301
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
264
302
#[ inline]
265
303
pub fn encode_lower_to_fmt < Ck : Checksum , W : fmt:: Write > (
266
304
fmt : & mut W ,
267
305
hrp : Hrp ,
268
306
data : & [ u8 ] ,
269
- ) -> Result < ( ) , fmt :: Error > {
307
+ ) -> Result < ( ) , EncodeError > {
270
308
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
271
309
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
272
310
for c in chars {
@@ -279,12 +317,16 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
279
317
///
280
318
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
281
319
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
320
+ ///
321
+ /// ## Deviation from spec (BIP-173)
322
+ ///
323
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
282
324
#[ inline]
283
325
pub fn encode_upper_to_fmt < Ck : Checksum , W : fmt:: Write > (
284
326
fmt : & mut W ,
285
327
hrp : Hrp ,
286
328
data : & [ u8 ] ,
287
- ) -> Result < ( ) , fmt :: Error > {
329
+ ) -> Result < ( ) , EncodeError > {
288
330
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
289
331
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
290
332
for c in chars {
@@ -297,27 +339,35 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
297
339
///
298
340
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
299
341
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
342
+ ///
343
+ /// ## Deviation from spec (BIP-173)
344
+ ///
345
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
300
346
#[ cfg( feature = "std" ) ]
301
347
#[ inline]
302
348
pub fn encode_to_writer < Ck : Checksum , W : std:: io:: Write > (
303
349
w : & mut W ,
304
350
hrp : Hrp ,
305
351
data : & [ u8 ] ,
306
- ) -> Result < ( ) , std :: io :: Error > {
352
+ ) -> Result < ( ) , EncodeIoError > {
307
353
encode_lower_to_writer :: < Ck , W > ( w, hrp, data)
308
354
}
309
355
310
356
/// Encodes `data` to a writer ([`std::io::Write`]) as a lowercase bech32 encoded string.
311
357
///
312
358
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
313
359
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
360
+ ///
361
+ /// ## Deviation from spec (BIP-173)
362
+ ///
363
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
314
364
#[ cfg( feature = "std" ) ]
315
365
#[ inline]
316
366
pub fn encode_lower_to_writer < Ck : Checksum , W : std:: io:: Write > (
317
367
w : & mut W ,
318
368
hrp : Hrp ,
319
369
data : & [ u8 ] ,
320
- ) -> Result < ( ) , std :: io :: Error > {
370
+ ) -> Result < ( ) , EncodeIoError > {
321
371
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
322
372
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
323
373
for c in chars {
@@ -330,13 +380,17 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
330
380
///
331
381
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
332
382
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
383
+ ///
384
+ /// ## Deviation from spec (BIP-173)
385
+ ///
386
+ /// We only restrict the total length of the encoded string to 1023 characters (not 90).
333
387
#[ cfg( feature = "std" ) ]
334
388
#[ inline]
335
389
pub fn encode_upper_to_writer < Ck : Checksum , W : std:: io:: Write > (
336
390
w : & mut W ,
337
391
hrp : Hrp ,
338
392
data : & [ u8 ] ,
339
- ) -> Result < ( ) , std :: io :: Error > {
393
+ ) -> Result < ( ) , EncodeIoError > {
340
394
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
341
395
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
342
396
for c in chars {
@@ -392,6 +446,87 @@ impl From<UncheckedHrpstringError> for DecodeError {
392
446
fn from ( e : UncheckedHrpstringError ) -> Self { Self :: Parse ( e) }
393
447
}
394
448
449
+ /// An error while encoding a bech32 string.
450
+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
451
+ #[ non_exhaustive]
452
+ pub enum EncodeError {
453
+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
454
+ TooLong ( usize ) ,
455
+ /// Encode to formatter failed.
456
+ Fmt ( fmt:: Error ) ,
457
+ }
458
+
459
+ impl fmt:: Display for EncodeError {
460
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
461
+ use EncodeError :: * ;
462
+
463
+ match * self {
464
+ TooLong ( len) =>
465
+ write ! ( f, "encoded length {} exceeds spec limit {} chars" , len, MAX_STRING_LENGTH ) ,
466
+ Fmt ( ref e) => write_err ! ( f, "encode to formatter failed" ; e) ,
467
+ }
468
+ }
469
+ }
470
+
471
+ #[ cfg( feature = "std" ) ]
472
+ impl std:: error:: Error for EncodeError {
473
+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
474
+ use EncodeError :: * ;
475
+
476
+ match * self {
477
+ TooLong ( _) => None ,
478
+ Fmt ( ref e) => Some ( e) ,
479
+ }
480
+ }
481
+ }
482
+
483
+ impl From < fmt:: Error > for EncodeError {
484
+ #[ inline]
485
+ fn from ( e : fmt:: Error ) -> Self { Self :: Fmt ( e) }
486
+ }
487
+
488
+ /// An error while encoding a bech32 string.
489
+ #[ cfg( feature = "std" ) ]
490
+ #[ derive( Debug ) ]
491
+ #[ non_exhaustive]
492
+ pub enum EncodeIoError {
493
+ /// Encoding HRP and data into a bech32 string exceeds maximum allowed.
494
+ TooLong ( usize ) ,
495
+ /// Encode to writer failed.
496
+ Write ( std:: io:: Error ) ,
497
+ }
498
+
499
+ #[ cfg( feature = "std" ) ]
500
+ impl fmt:: Display for EncodeIoError {
501
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
502
+ use EncodeIoError :: * ;
503
+
504
+ match * self {
505
+ TooLong ( len) =>
506
+ write ! ( f, "encoded length {} exceeds spec limit {} chars" , len, MAX_STRING_LENGTH ) ,
507
+ Write ( ref e) => write_err ! ( f, "encode to writer failed" ; e) ,
508
+ }
509
+ }
510
+ }
511
+
512
+ #[ cfg( feature = "std" ) ]
513
+ impl std:: error:: Error for EncodeIoError {
514
+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
515
+ use EncodeIoError :: * ;
516
+
517
+ match * self {
518
+ TooLong ( _) => None ,
519
+ Write ( ref e) => Some ( e) ,
520
+ }
521
+ }
522
+ }
523
+
524
+ #[ cfg( feature = "std" ) ]
525
+ impl From < std:: io:: Error > for EncodeIoError {
526
+ #[ inline]
527
+ fn from ( e : std:: io:: Error ) -> Self { Self :: Write ( e) }
528
+ }
529
+
395
530
#[ cfg( test) ]
396
531
#[ cfg( feature = "alloc" ) ]
397
532
mod tests {
@@ -493,4 +628,31 @@ mod tests {
493
628
494
629
assert_eq ! ( got, want) ;
495
630
}
631
+
632
+ #[ test]
633
+ fn can_encode_maximum_length_string ( ) {
634
+ let data = [ 0_u8 ; 632 ] ;
635
+ let hrp = Hrp :: parse_unchecked ( "abcd" ) ;
636
+ let s = encode :: < Bech32m > ( hrp, & data) . expect ( "failed to encode string" ) ;
637
+ assert_eq ! ( s. len( ) , 1023 ) ;
638
+ }
639
+
640
+ #[ test]
641
+ fn can_not_encode_string_too_long ( ) {
642
+ let data = [ 0_u8 ; 632 ] ;
643
+ let hrp = Hrp :: parse_unchecked ( "abcde" ) ;
644
+
645
+ match encode :: < Bech32m > ( hrp, & data) {
646
+ Ok ( _) => panic ! ( "false positive" ) ,
647
+ Err ( EncodeError :: TooLong ( len) ) => assert_eq ! ( len, 1024 ) ,
648
+ _ => panic ! ( "false negative" ) ,
649
+ }
650
+ }
651
+
652
+ #[ test]
653
+ fn can_decode_segwit_too_long_string ( ) {
654
+ // A 91 character long string, greater than the segwit enforced maximum of 90.
655
+ let s = "abcd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrw9z3s" ;
656
+ assert ! ( decode( s) . is_ok( ) ) ;
657
+ }
496
658
}
0 commit comments