23
23
//! The original description in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
24
24
//! has more details. See also [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki).
25
25
//!
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
+ //!
26
32
//! # Examples
27
33
//!
28
34
//! ## Encoding
100
106
//! impl Checksum for Codex32 {
101
107
//! type MidstateRepr = u128;
102
108
//! const CHECKSUM_LENGTH: usize = 13;
109
+ //! const CODE_LENGTH: usize = 93;
103
110
//! // Copied from BIP-93
104
111
//! const GENERATOR_SH: [u128; 5] = [
105
112
//! 0x19dc500ce73fde210,
113
120
//!
114
121
//! # }
115
122
//! ```
123
+ //!
124
+ //! [`Checksum::CODE_LENGTH`]: crate::primitives::checksum::Checksum::CODE_LENGTH
116
125
117
126
#![ cfg_attr( all( not( feature = "std" ) , not( test) ) , no_std) ]
118
127
// Experimental features we need.
@@ -142,14 +151,12 @@ pub mod segwit;
142
151
use alloc:: { string:: String , vec:: Vec } ;
143
152
use core:: fmt;
144
153
145
- #[ cfg( feature = "alloc" ) ]
146
154
use crate :: error:: write_err;
147
155
#[ cfg( doc) ]
148
156
use crate :: primitives:: decode:: CheckedHrpstring ;
157
+ use crate :: primitives:: decode:: CodeLengthError ;
149
158
#[ 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 } ;
153
160
154
161
#[ rustfmt:: skip] // Keep public re-exports separate.
155
162
#[ doc( inline) ]
@@ -216,7 +223,7 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
216
223
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
217
224
#[ cfg( feature = "alloc" ) ]
218
225
#[ 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 > {
220
227
encode_lower :: < Ck > ( hrp, data)
221
228
}
222
229
@@ -226,7 +233,7 @@ pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error>
226
233
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
227
234
#[ cfg( feature = "alloc" ) ]
228
235
#[ 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 > {
230
237
let mut buf = String :: new ( ) ;
231
238
encode_lower_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
232
239
Ok ( buf)
@@ -238,7 +245,7 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
238
245
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
239
246
#[ cfg( feature = "alloc" ) ]
240
247
#[ 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 > {
242
249
let mut buf = String :: new ( ) ;
243
250
encode_upper_to_fmt :: < Ck , String > ( & mut buf, hrp, data) ?;
244
251
Ok ( buf)
@@ -253,7 +260,7 @@ pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
253
260
fmt : & mut W ,
254
261
hrp : Hrp ,
255
262
data : & [ u8 ] ,
256
- ) -> Result < ( ) , fmt :: Error > {
263
+ ) -> Result < ( ) , EncodeError > {
257
264
encode_lower_to_fmt :: < Ck , W > ( fmt, hrp, data)
258
265
}
259
266
@@ -266,7 +273,9 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
266
273
fmt : & mut W ,
267
274
hrp : Hrp ,
268
275
data : & [ u8 ] ,
269
- ) -> Result < ( ) , fmt:: Error > {
276
+ ) -> Result < ( ) , EncodeError > {
277
+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
278
+
270
279
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
271
280
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
272
281
for c in chars {
@@ -284,7 +293,9 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
284
293
fmt : & mut W ,
285
294
hrp : Hrp ,
286
295
data : & [ u8 ] ,
287
- ) -> Result < ( ) , fmt:: Error > {
296
+ ) -> Result < ( ) , EncodeError > {
297
+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
298
+
288
299
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
289
300
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
290
301
for c in chars {
@@ -303,7 +314,7 @@ pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
303
314
w : & mut W ,
304
315
hrp : Hrp ,
305
316
data : & [ u8 ] ,
306
- ) -> Result < ( ) , std :: io :: Error > {
317
+ ) -> Result < ( ) , EncodeIoError > {
307
318
encode_lower_to_writer :: < Ck , W > ( w, hrp, data)
308
319
}
309
320
@@ -317,7 +328,9 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
317
328
w : & mut W ,
318
329
hrp : Hrp ,
319
330
data : & [ u8 ] ,
320
- ) -> Result < ( ) , std:: io:: Error > {
331
+ ) -> Result < ( ) , EncodeIoError > {
332
+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
333
+
321
334
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
322
335
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
323
336
for c in chars {
@@ -336,7 +349,9 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
336
349
w : & mut W ,
337
350
hrp : Hrp ,
338
351
data : & [ u8 ] ,
339
- ) -> Result < ( ) , std:: io:: Error > {
352
+ ) -> Result < ( ) , EncodeIoError > {
353
+ let _ = encoded_length :: < Ck > ( hrp, data) ?;
354
+
340
355
let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
341
356
let chars = iter. with_checksum :: < Ck > ( & hrp) . chars ( ) ;
342
357
for c in chars {
@@ -345,6 +360,25 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
345
360
Ok ( ( ) )
346
361
}
347
362
363
+ /// Checks that encoding `hrp` and `data` creates a code that is less than 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 > {
373
+ let iter = data. iter ( ) . copied ( ) . bytes_to_fes ( ) ;
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
+ }
380
+ }
381
+
348
382
/// An error while decoding a bech32 string.
349
383
#[ cfg( feature = "alloc" ) ]
350
384
#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -386,11 +420,101 @@ impl From<UncheckedHrpstringError> for DecodeError {
386
420
fn from ( e : UncheckedHrpstringError ) -> Self { Self :: Parse ( e) }
387
421
}
388
422
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
+
389
513
#[ cfg( test) ]
390
514
#[ cfg( feature = "alloc" ) ]
391
515
mod tests {
392
516
use super :: * ;
393
- use crate :: Bech32 ;
517
+ use crate :: { Bech32 , Bech32m } ;
394
518
395
519
// Tests below using this data, are based on the test vector (from BIP-173):
396
520
// BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4: 0014751e76e8199196d454941c45d1b3a323f1433bd6
@@ -475,4 +599,44 @@ mod tests {
475
599
assert_eq ! ( hrp, Hrp :: parse_unchecked( "TEST" ) ) ;
476
600
assert_eq ! ( data, DATA ) ;
477
601
}
602
+
603
+ #[ test]
604
+ fn encoded_length_works ( ) {
605
+ let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky" ;
606
+ let ( hrp, data) = decode ( s) . expect ( "valid string" ) ;
607
+
608
+ let encoded = encode :: < Bech32m > ( hrp, & data) . expect ( "valid data" ) ;
609
+ let want = encoded. len ( ) ;
610
+ let got = encoded_length :: < Bech32m > ( hrp, & data) . expect ( "encoded length" ) ;
611
+
612
+ assert_eq ! ( got, want) ;
613
+ }
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
+ }
478
642
}
0 commit comments