7676use core:: { fmt, iter, slice, str} ;
7777
7878use crate :: error:: write_err;
79- use crate :: primitives:: checksum:: { self , Checksum } ;
79+ use crate :: primitives:: checksum:: { self , Checksum , PackedFe32 } ;
8080use crate :: primitives:: gf32:: Fe32 ;
8181use crate :: primitives:: hrp:: { self , Hrp } ;
8282use crate :: primitives:: iter:: { Fe32IterExt , FesToBytes } ;
8383use crate :: primitives:: segwit:: { self , WitnessLengthError , VERSION_0 } ;
84+ use crate :: primitives:: Polynomial ;
8485use crate :: { Bech32 , Bech32m } ;
8586
8687/// Separator between the hrp and payload (as defined by BIP-173).
@@ -277,8 +278,9 @@ impl<'s> UncheckedHrpstring<'s> {
277278 checksum_eng. input_fe ( fe) ;
278279 }
279280
280- if checksum_eng. residue ( ) != & Ck :: TARGET_RESIDUE {
281- return Err ( InvalidResidue ) ;
281+ let residue = * checksum_eng. residue ( ) ;
282+ if residue != Ck :: TARGET_RESIDUE {
283+ return Err ( InvalidResidue ( InvalidResidueError :: new ( residue, Ck :: TARGET_RESIDUE ) ) ) ;
282284 }
283285
284286 Ok ( ( ) )
@@ -952,7 +954,7 @@ pub enum ChecksumError {
952954 /// String exceeds maximum allowed length.
953955 CodeLength ( CodeLengthError ) ,
954956 /// The checksum residue is not valid for the data.
955- InvalidResidue ,
957+ InvalidResidue ( InvalidResidueError ) ,
956958 /// The checksummed string is not a valid length.
957959 InvalidLength ,
958960}
@@ -963,7 +965,7 @@ impl fmt::Display for ChecksumError {
963965
964966 match * self {
965967 CodeLength ( ref e) => write_err ! ( f, "string exceeds maximum allowed length" ; e) ,
966- InvalidResidue => write ! ( f, "the checksum residue is not valid for the data" ) ,
968+ InvalidResidue ( ref e ) => write_err ! ( f, "checksum failed" ; e ) ,
967969 InvalidLength => write ! ( f, "the checksummed string is not a valid length" ) ,
968970 }
969971 }
@@ -976,11 +978,56 @@ impl std::error::Error for ChecksumError {
976978
977979 match * self {
978980 CodeLength ( ref e) => Some ( e) ,
979- InvalidResidue | InvalidLength => None ,
981+ InvalidResidue ( ref e) => Some ( e) ,
982+ InvalidLength => None ,
980983 }
981984 }
982985}
983986
987+ /// Residue mismatch validating the checksum. That is, "the checksum failed".
988+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
989+ pub struct InvalidResidueError {
990+ actual : Polynomial < Fe32 > ,
991+ target : Polynomial < Fe32 > ,
992+ }
993+
994+ impl fmt:: Display for InvalidResidueError {
995+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
996+ if self . actual . has_data ( ) {
997+ write ! ( f, "residue {} did not match target {}" , self . actual, self . target)
998+ } else {
999+ f. write_str ( "residue mismatch" )
1000+ }
1001+ }
1002+ }
1003+
1004+ impl InvalidResidueError {
1005+ /// Constructs a new "invalid residue" error.
1006+ fn new < F : PackedFe32 > ( residue : F , target_residue : F ) -> Self {
1007+ Self {
1008+ actual : Polynomial :: from_residue ( residue) ,
1009+ target : Polynomial :: from_residue ( target_residue) ,
1010+ }
1011+ }
1012+
1013+ /// Whether this "invalid residue" error actually represents a valid residue
1014+ /// for the bech32 checksum.
1015+ ///
1016+ /// This method could in principle be made generic over the intended checksum,
1017+ /// but it is not clear what the purpose would be (checking bech32 vs bech32m
1018+ /// is a special case), and the method would necessarily panic if called with
1019+ /// too large a checksum without an allocator. We would like to better understand
1020+ /// the usecase for this before exposing such a footgun.
1021+ pub fn matches_bech32_checksum ( & self ) -> bool {
1022+ self . actual == Polynomial :: from_residue ( Bech32 :: TARGET_RESIDUE )
1023+ }
1024+ }
1025+
1026+ #[ cfg( feature = "std" ) ]
1027+ impl std:: error:: Error for InvalidResidueError {
1028+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > { None }
1029+ }
1030+
9841031/// Encoding HRP and data into a bech32 string exceeds the checksum code length.
9851032#[ derive( Debug , Clone , PartialEq , Eq ) ]
9861033#[ non_exhaustive]
@@ -1065,6 +1112,9 @@ impl std::error::Error for PaddingError {
10651112
10661113#[ cfg( test) ]
10671114mod tests {
1115+ #[ cfg( all( feature = "alloc" , not( feature = "std" ) ) ) ]
1116+ use alloc:: vec:: Vec ;
1117+
10681118 use super :: * ;
10691119
10701120 #[ test]
@@ -1117,7 +1167,7 @@ mod tests {
11171167 . expect ( "string parses correctly" )
11181168 . validate_checksum :: < Bech32 > ( )
11191169 . unwrap_err ( ) ;
1120- assert_eq ! ( err, InvalidResidue ) ;
1170+ assert ! ( matches! ( err, InvalidResidue ( .. ) ) ) ;
11211171 }
11221172
11231173 #[ test]
@@ -1178,7 +1228,7 @@ mod tests {
11781228 . unwrap ( )
11791229 . validate_checksum :: < Bech32m > ( )
11801230 . unwrap_err ( ) ;
1181- assert_eq ! ( err, InvalidResidue ) ;
1231+ assert ! ( matches! ( err, InvalidResidue ( .. ) ) ) ;
11821232 }
11831233
11841234 #[ test]
0 commit comments