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.
@@ -144,6 +157,7 @@ use core::fmt;
144
157
145
158
#[ cfg( feature = "alloc" ) ]
146
159
use crate :: error:: write_err;
160
+ use crate :: primitives:: checksum:: MAX_STRING_LENGTH ;
147
161
#[ cfg( doc) ]
148
162
use crate :: primitives:: decode:: CheckedHrpstring ;
149
163
#[ cfg( feature = "alloc" ) ]
@@ -214,19 +228,32 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
214
228
///
215
229
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
216
230
/// `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).
217
235
#[ cfg( feature = "alloc" ) ]
218
236
#[ 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
+
220
243
encode_lower :: < Ck > ( hrp, data)
221
244
}
222
245
223
246
/// Encodes `data` as a lowercase bech32 encoded string.
224
247
///
225
248
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
226
249
/// `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).
227
254
#[ cfg( feature = "alloc" ) ]
228
255
#[ 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 > {
230
257
let mut buf = String :: new ( ) ;
231
258
encode_lower_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
232
259
Ok ( buf)
@@ -236,9 +263,13 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
236
263
///
237
264
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
238
265
/// `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).
239
270
#[ cfg( feature = "alloc" ) ]
240
271
#[ 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 > {
242
273
let mut buf = String :: new ( ) ;
243
274
encode_upper_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
244
275
Ok ( buf)
@@ -248,25 +279,33 @@ pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
248
279
///
249
280
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
250
281
/// `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).
251
286
#[ inline]
252
287
pub fn encode_to_fmt < Ck : Checksum , W : fmt:: Write > (
253
288
fmt : & mut W ,
254
289
hrp : Hrp ,
255
290
data : & [ u8 ] ,
256
- ) -> Result < ( ) , fmt :: Error > {
291
+ ) -> Result < ( ) , EncodeError > {
257
292
encode_lower_to_fmt :: < Ck , W > ( fmt, hrp, data)
258
293
}
259
294
260
295
/// Encodes `data` to a writer ([`fmt::Write`]) as a lowercase bech32 encoded string.
261
296
///
262
297
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
263
298
/// `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).
264
303
#[ inline]
265
304
pub fn encode_lower_to_fmt < Ck : Checksum , W : fmt:: Write > (
266
305
fmt : & mut W ,
267
306
hrp : Hrp ,
268
307
data : & [ u8 ] ,
269
- ) -> Result < ( ) , fmt :: Error > {
308
+ ) -> Result < ( ) , EncodeError > {
270
309
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
271
310
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
272
311
for c in chars {
@@ -279,12 +318,16 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
279
318
///
280
319
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
281
320
/// `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).
282
325
#[ inline]
283
326
pub fn encode_upper_to_fmt < Ck : Checksum , W : fmt:: Write > (
284
327
fmt : & mut W ,
285
328
hrp : Hrp ,
286
329
data : & [ u8 ] ,
287
- ) -> Result < ( ) , fmt :: Error > {
330
+ ) -> Result < ( ) , EncodeError > {
288
331
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
289
332
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
290
333
for c in chars {
@@ -297,27 +340,35 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
297
340
///
298
341
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
299
342
/// `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).
300
347
#[ cfg( feature = "std" ) ]
301
348
#[ inline]
302
349
pub fn encode_to_writer < Ck : Checksum , W : std:: io:: Write > (
303
350
w : & mut W ,
304
351
hrp : Hrp ,
305
352
data : & [ u8 ] ,
306
- ) -> Result < ( ) , std :: io :: Error > {
353
+ ) -> Result < ( ) , EncodeIoError > {
307
354
encode_lower_to_writer :: < Ck , W > ( w, hrp, data)
308
355
}
309
356
310
357
/// Encodes `data` to a writer ([`std::io::Write`]) as a lowercase bech32 encoded string.
311
358
///
312
359
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
313
360
/// `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).
314
365
#[ cfg( feature = "std" ) ]
315
366
#[ inline]
316
367
pub fn encode_lower_to_writer < Ck : Checksum , W : std:: io:: Write > (
317
368
w : & mut W ,
318
369
hrp : Hrp ,
319
370
data : & [ u8 ] ,
320
- ) -> Result < ( ) , std :: io :: Error > {
371
+ ) -> Result < ( ) , EncodeIoError > {
321
372
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
322
373
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
323
374
for c in chars {
@@ -330,13 +381,17 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
330
381
///
331
382
/// Encoded string will be prefixed with the `hrp` and have a checksum appended as specified by the
332
383
/// `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).
333
388
#[ cfg( feature = "std" ) ]
334
389
#[ inline]
335
390
pub fn encode_upper_to_writer < Ck : Checksum , W : std:: io:: Write > (
336
391
w : & mut W ,
337
392
hrp : Hrp ,
338
393
data : & [ u8 ] ,
339
- ) -> Result < ( ) , std :: io :: Error > {
394
+ ) -> Result < ( ) , EncodeIoError > {
340
395
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
341
396
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
342
397
for c in chars {
@@ -392,6 +447,88 @@ impl From<UncheckedHrpstringError> for DecodeError {
392
447
fn from ( e : UncheckedHrpstringError ) -> Self { Self :: Parse ( e) }
393
448
}
394
449
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
+
395
532
#[ cfg( test) ]
396
533
#[ cfg( feature = "alloc" ) ]
397
534
mod tests {
@@ -493,4 +630,31 @@ mod tests {
493
630
494
631
assert_eq ! ( got, want) ;
495
632
}
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
+ }
496
660
}
0 commit comments