@@ -128,6 +128,8 @@ pub trait WriteBase32 {
128
128
fn write_u5 ( & mut self , data : u5 ) -> Result < ( ) , Self :: Err > ;
129
129
}
130
130
131
+ const CHECKSUM_LENGTH : usize = 6 ;
132
+
131
133
/// Allocationless Bech32 writer that accumulates the checksum data internally and writes them out
132
134
/// in the end.
133
135
pub struct Bech32Writer < ' a > {
@@ -180,27 +182,28 @@ impl<'a> Bech32Writer<'a> {
180
182
181
183
/// Write out the checksum at the end. If this method isn't called this will happen on drop.
182
184
pub fn finalize ( mut self ) -> fmt:: Result {
183
- self . inner_finalize ( ) ?;
185
+ self . write_checksum ( ) ?;
184
186
mem:: forget ( self ) ;
185
187
Ok ( ( ) )
186
188
}
187
189
188
- fn inner_finalize ( & mut self ) -> fmt:: Result {
190
+ fn write_checksum ( & mut self ) -> fmt:: Result {
189
191
// Pad with 6 zeros
190
- for _ in 0 ..6 {
192
+ for _ in 0 ..CHECKSUM_LENGTH {
191
193
self . polymod_step ( u5 ( 0 ) )
192
194
}
193
195
194
196
let plm: u32 = self . chk ^ self . variant . constant ( ) ;
195
197
196
- for p in 0 ..6 {
198
+ for p in 0 ..CHECKSUM_LENGTH {
197
199
self . formatter
198
200
. write_char ( u5 ( ( ( plm >> ( 5 * ( 5 - p) ) ) & 0x1f ) as u8 ) . to_char ( ) ) ?;
199
201
}
200
202
201
203
Ok ( ( ) )
202
204
}
203
205
}
206
+
204
207
impl < ' a > WriteBase32 for Bech32Writer < ' a > {
205
208
type Err = fmt:: Error ;
206
209
@@ -213,7 +216,7 @@ impl<'a> WriteBase32 for Bech32Writer<'a> {
213
216
214
217
impl < ' a > Drop for Bech32Writer < ' a > {
215
218
fn drop ( & mut self ) {
216
- self . inner_finalize ( )
219
+ self . write_checksum ( )
217
220
. expect ( "Unhandled error writing the checksum on drop." )
218
221
}
219
222
}
@@ -398,28 +401,51 @@ fn check_hrp(hrp: &str) -> Result<Case, Error> {
398
401
///
399
402
/// # Errors
400
403
/// * If [check_hrp] returns an error for the given HRP.
404
+ /// * If `fmt` fails on write
401
405
/// # Deviations from standard
402
406
/// * No length limits are enforced for the data part
403
407
pub fn encode_to_fmt < T : AsRef < [ u5 ] > > (
404
408
fmt : & mut fmt:: Write ,
405
409
hrp : & str ,
406
410
data : T ,
407
411
variant : Variant ,
408
- ) -> Result < fmt :: Result , Error > {
412
+ ) -> Result < ( ) , Error > {
409
413
let hrp_lower = match check_hrp ( hrp) ? {
410
414
Case :: Upper => Cow :: Owned ( hrp. to_lowercase ( ) ) ,
411
415
Case :: Lower | Case :: None => Cow :: Borrowed ( hrp) ,
412
416
} ;
413
417
414
- match Bech32Writer :: new ( & hrp_lower, variant, fmt) {
415
- Ok ( mut writer) => {
416
- Ok ( writer. write ( data. as_ref ( ) ) . and_then ( |_| {
417
- // Finalize manually to avoid panic on drop if write fails
418
- writer. finalize ( )
419
- } ) )
420
- }
421
- Err ( e) => Ok ( Err ( e) ) ,
418
+ let mut writer = Bech32Writer :: new ( & hrp_lower, variant, fmt) ?;
419
+ Ok ( writer. write ( data. as_ref ( ) ) . and_then ( |_| {
420
+ // Finalize manually to avoid panic on drop if write fails
421
+ writer. finalize ( )
422
+ } ) ?)
423
+ }
424
+
425
+ /// Encode a bech32 payload without a checksum to an [fmt::Write].
426
+ /// This method is intended for implementing traits from [std::fmt].
427
+ ///
428
+ /// # Errors
429
+ /// * If [check_hrp] returns an error for the given HRP.
430
+ /// * If `fmt` fails on write
431
+ /// # Deviations from standard
432
+ /// * No length limits are enforced for the data part
433
+ pub fn encode_without_checksum_to_fmt < T : AsRef < [ u5 ] > > (
434
+ fmt : & mut fmt:: Write ,
435
+ hrp : & str ,
436
+ data : T ,
437
+ ) -> Result < ( ) , Error > {
438
+ let hrp = match check_hrp ( hrp) ? {
439
+ Case :: Upper => Cow :: Owned ( hrp. to_lowercase ( ) ) ,
440
+ Case :: Lower | Case :: None => Cow :: Borrowed ( hrp) ,
441
+ } ;
442
+
443
+ fmt. write_str ( & hrp) ?;
444
+ fmt. write_char ( SEP ) ?;
445
+ for b in data. as_ref ( ) {
446
+ fmt. write_char ( b. to_char ( ) ) ?;
422
447
}
448
+ Ok ( ( ) )
423
449
}
424
450
425
451
/// Used for encode/decode operations for the two variants of Bech32
@@ -460,19 +486,52 @@ impl Variant {
460
486
/// * No length limits are enforced for the data part
461
487
pub fn encode < T : AsRef < [ u5 ] > > ( hrp : & str , data : T , variant : Variant ) -> Result < String , Error > {
462
488
let mut buf = String :: new ( ) ;
463
- encode_to_fmt ( & mut buf, hrp, data, variant) ?. unwrap ( ) ;
489
+ encode_to_fmt ( & mut buf, hrp, data, variant) ?;
490
+ Ok ( buf)
491
+ }
492
+
493
+ /// Encode a bech32 payload to string without the checksum.
494
+ ///
495
+ /// # Errors
496
+ /// * If [check_hrp] returns an error for the given HRP.
497
+ /// # Deviations from standard
498
+ /// * No length limits are enforced for the data part
499
+ pub fn encode_without_checksum < T : AsRef < [ u5 ] > > ( hrp : & str , data : T ) -> Result < String , Error > {
500
+ let mut buf = String :: new ( ) ;
501
+ encode_without_checksum_to_fmt ( & mut buf, hrp, data) ?;
464
502
Ok ( buf)
465
503
}
466
504
467
505
/// Decode a bech32 string into the raw HRP and the data bytes.
468
506
///
469
- /// Returns the HRP in lowercase. .
507
+ /// Returns the HRP in lowercase, the data with the checksum removed, and the encoding .
470
508
pub fn decode ( s : & str ) -> Result < ( String , Vec < u5 > , Variant ) , Error > {
471
- // Ensure overall length is within bounds
472
- if s . len ( ) < 8 {
509
+ let ( hrp_lower , mut data ) = split_and_decode ( s ) ? ;
510
+ if data . len ( ) < CHECKSUM_LENGTH {
473
511
return Err ( Error :: InvalidLength ) ;
474
512
}
475
513
514
+ // Ensure checksum
515
+ match verify_checksum ( hrp_lower. as_bytes ( ) , & data) {
516
+ Some ( variant) => {
517
+ // Remove checksum from data payload
518
+ data. truncate ( data. len ( ) - CHECKSUM_LENGTH ) ;
519
+
520
+ Ok ( ( hrp_lower, data, variant) )
521
+ }
522
+ None => Err ( Error :: InvalidChecksum ) ,
523
+ }
524
+ }
525
+
526
+ /// Decode a bech32 string into the raw HRP and the data bytes, assuming no checksum.
527
+ ///
528
+ /// Returns the HRP in lowercase and the data.
529
+ pub fn decode_without_checksum ( s : & str ) -> Result < ( String , Vec < u5 > ) , Error > {
530
+ split_and_decode ( s)
531
+ }
532
+
533
+ /// Decode a bech32 string into the raw HRP and the `u5` data.
534
+ fn split_and_decode ( s : & str ) -> Result < ( String , Vec < u5 > ) , Error > {
476
535
// Split at separator and check for two pieces
477
536
let ( raw_hrp, raw_data) = match s. rfind ( SEP ) {
478
537
None => return Err ( Error :: MissingSeparator ) ,
@@ -481,9 +540,6 @@ pub fn decode(s: &str) -> Result<(String, Vec<u5>, Variant), Error> {
481
540
( hrp, & data[ 1 ..] )
482
541
}
483
542
} ;
484
- if raw_data. len ( ) < 6 {
485
- return Err ( Error :: InvalidLength ) ;
486
- }
487
543
488
544
let mut case = check_hrp ( raw_hrp) ?;
489
545
let hrp_lower = match case {
@@ -493,7 +549,7 @@ pub fn decode(s: &str) -> Result<(String, Vec<u5>, Variant), Error> {
493
549
} ;
494
550
495
551
// Check data payload
496
- let mut data = raw_data
552
+ let data = raw_data
497
553
. chars ( )
498
554
. map ( |c| {
499
555
// Only check if c is in the ASCII range, all invalid ASCII
@@ -528,17 +584,7 @@ pub fn decode(s: &str) -> Result<(String, Vec<u5>, Variant), Error> {
528
584
} )
529
585
. collect :: < Result < Vec < u5 > , Error > > ( ) ?;
530
586
531
- // Ensure checksum
532
- match verify_checksum ( hrp_lower. as_bytes ( ) , & data) {
533
- Some ( variant) => {
534
- // Remove checksum from data payload
535
- let dbl: usize = data. len ( ) ;
536
- data. truncate ( dbl - 6 ) ;
537
-
538
- Ok ( ( hrp_lower, data, variant) )
539
- }
540
- None => Err ( Error :: InvalidChecksum ) ,
541
- }
587
+ Ok ( ( hrp_lower, data) )
542
588
}
543
589
544
590
fn verify_checksum ( hrp : & [ u8 ] , data : & [ u5 ] ) -> Option < Variant > {
@@ -622,6 +668,14 @@ pub enum Error {
622
668
InvalidPadding ,
623
669
/// The whole string must be of one case
624
670
MixedCase ,
671
+ /// Writing UTF-8 data failed
672
+ WriteFailure ( fmt:: Error ) ,
673
+ }
674
+
675
+ impl From < fmt:: Error > for Error {
676
+ fn from ( error : fmt:: Error ) -> Self {
677
+ Self :: WriteFailure ( error)
678
+ }
625
679
}
626
680
627
681
impl fmt:: Display for Error {
@@ -634,6 +688,7 @@ impl fmt::Display for Error {
634
688
Error :: InvalidData ( n) => write ! ( f, "invalid data point ({})" , n) ,
635
689
Error :: InvalidPadding => write ! ( f, "invalid padding" ) ,
636
690
Error :: MixedCase => write ! ( f, "mixed-case strings not allowed" ) ,
691
+ Error :: WriteFailure ( _) => write ! ( f, "failed writing utf-8 data" ) ,
637
692
}
638
693
}
639
694
}
@@ -649,6 +704,7 @@ impl std::error::Error for Error {
649
704
Error :: InvalidData ( _) => "invalid data point" ,
650
705
Error :: InvalidPadding => "invalid padding" ,
651
706
Error :: MixedCase => "mixed-case strings not allowed" ,
707
+ Error :: WriteFailure ( _) => "failed writing utf-8 data" ,
652
708
}
653
709
}
654
710
}
@@ -792,6 +848,8 @@ mod tests {
792
848
Error :: InvalidLength ) ,
793
849
( "1p2gdwpf" ,
794
850
Error :: InvalidLength ) ,
851
+ ( "bc1p2" ,
852
+ Error :: InvalidLength ) ,
795
853
) ;
796
854
for p in pairs {
797
855
let ( s, expected_error) = p;
@@ -921,7 +979,7 @@ mod tests {
921
979
}
922
980
923
981
#[ test]
924
- fn writer ( ) {
982
+ fn write_with_checksum ( ) {
925
983
let hrp = "lnbc" ;
926
984
let data = "Hello World!" . as_bytes ( ) . to_base32 ( ) ;
927
985
@@ -938,7 +996,26 @@ mod tests {
938
996
}
939
997
940
998
#[ test]
941
- fn write_on_drop ( ) {
999
+ fn write_without_checksum ( ) {
1000
+ let hrp = "lnbc" ;
1001
+ let data = "Hello World!" . as_bytes ( ) . to_base32 ( ) ;
1002
+
1003
+ let mut written_str = String :: new ( ) ;
1004
+ {
1005
+ let mut writer = Bech32Writer :: new ( hrp, Variant :: Bech32 , & mut written_str) . unwrap ( ) ;
1006
+ writer. write ( & data) . unwrap ( ) ;
1007
+ }
1008
+
1009
+ let encoded_str = encode_without_checksum ( hrp, data) . unwrap ( ) ;
1010
+
1011
+ assert_eq ! (
1012
+ encoded_str,
1013
+ written_str[ ..written_str. len( ) - CHECKSUM_LENGTH ]
1014
+ ) ;
1015
+ }
1016
+
1017
+ #[ test]
1018
+ fn write_with_checksum_on_drop ( ) {
942
1019
let hrp = "lntb" ;
943
1020
let data = "Hello World!" . as_bytes ( ) . to_base32 ( ) ;
944
1021
@@ -953,6 +1030,19 @@ mod tests {
953
1030
assert_eq ! ( encoded_str, written_str) ;
954
1031
}
955
1032
1033
+ #[ test]
1034
+ fn roundtrip_without_checksum ( ) {
1035
+ let hrp = "lnbc" ;
1036
+ let data = "Hello World!" . as_bytes ( ) . to_base32 ( ) ;
1037
+
1038
+ let encoded = encode_without_checksum ( hrp, data. clone ( ) ) . expect ( "failed to encode" ) ;
1039
+ let ( decoded_hrp, decoded_data) =
1040
+ decode_without_checksum ( & encoded) . expect ( "failed to decode" ) ;
1041
+
1042
+ assert_eq ! ( decoded_hrp, hrp) ;
1043
+ assert_eq ! ( decoded_data, data) ;
1044
+ }
1045
+
956
1046
#[ test]
957
1047
fn test_hrp_case ( ) {
958
1048
// Tests for issue with HRP case checking being ignored for encoding
0 commit comments