@@ -16,44 +16,116 @@ use bech32::{Checksum, Fe32};
1616
1717pub use crate :: expression:: VALID_CHARS ;
1818use crate :: prelude:: * ;
19- use crate :: Error ;
2019
2120const CHECKSUM_LENGTH : usize = 8 ;
2221const CODE_LENGTH : usize = 32767 ;
2322
24- /// Compute the checksum of a descriptor.
23+ /// Map of valid characters in descriptor strings .
2524///
26- /// Note that this function does not check if the descriptor string is
27- /// syntactically correct or not. This only computes the checksum.
28- pub fn desc_checksum ( desc : & str ) -> Result < String , Error > {
29- let mut eng = Engine :: new ( ) ;
30- eng. input ( desc) ?;
31- Ok ( eng. checksum ( ) )
25+ /// The map starts at 32 (space) and runs up to 126 (tilde).
26+ #[ rustfmt:: skip]
27+ const CHAR_MAP : [ u8 ; 95 ] = [
28+ 94 , 59 , 92 , 91 , 28 , 29 , 50 , 15 , 10 , 11 , 17 , 51 , 14 , 52 , 53 , 16 ,
29+ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 27 , 54 , 55 , 56 , 57 , 58 ,
30+ 26 , 82 , 83 , 84 , 85 , 86 , 87 , 88 , 89 , 32 , 33 , 34 , 35 , 36 , 37 , 38 ,
31+ 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 12 , 93 , 13 , 60 , 61 ,
32+ 90 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 64 , 65 , 66 , 67 , 68 , 69 , 70 ,
33+ 71 , 72 , 73 , 74 , 75 , 76 , 77 , 78 , 79 , 80 , 81 , 30 , 62 , 31 , 63 ,
34+ ] ;
35+
36+ /// Error validating descriptor checksum.
37+ #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
38+ pub enum Error {
39+ /// Character outside of descriptor charset.
40+ InvalidCharacter {
41+ /// The character in question.
42+ ch : char ,
43+ /// Its position in the string.
44+ pos : usize ,
45+ } ,
46+ /// Checksum had the incorrect length.
47+ InvalidChecksumLength {
48+ /// The length of the checksum in the string.
49+ actual : usize ,
50+ /// The length of a valid descriptor checksum.
51+ expected : usize ,
52+ } ,
53+ /// Checksum was invalid.
54+ InvalidChecksum {
55+ /// The checksum in the string.
56+ actual : [ char ; CHECKSUM_LENGTH ] ,
57+ /// The checksum that should have been there, assuming the string is valid.
58+ expected : [ char ; CHECKSUM_LENGTH ] ,
59+ } ,
60+ }
61+
62+ impl fmt:: Display for Error {
63+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
64+ match * self {
65+ Error :: InvalidCharacter { ch, pos } => {
66+ write ! ( f, "invalid character '{}' (position {})" , ch, pos)
67+ }
68+ Error :: InvalidChecksumLength { actual, expected } => {
69+ write ! ( f, "invalid checksum (length {}, expected {})" , actual, expected)
70+ }
71+ Error :: InvalidChecksum { actual, expected } => {
72+ f. write_str ( "invalid checksum " ) ?;
73+ for ch in actual {
74+ ch. fmt ( f) ?;
75+ }
76+ f. write_str ( "; expected " ) ?;
77+ for ch in expected {
78+ ch. fmt ( f) ?;
79+ }
80+ Ok ( ( ) )
81+ }
82+ }
83+ }
84+ }
85+
86+ #[ cfg( feature = "std" ) ]
87+ impl std:: error:: Error for Error {
88+ fn cause ( & self ) -> Option < & dyn std:: error:: Error > { None }
3289}
3390
3491/// Helper function for `FromStr` for various descriptor types.
3592///
3693/// Checks and verifies the checksum if it is present and returns the descriptor
3794/// string without the checksum.
38- pub ( super ) fn verify_checksum ( s : & str ) -> Result < & str , Error > {
39- for ch in s. as_bytes ( ) {
40- if * ch < 20 || * ch > 127 {
41- return Err ( Error :: Unprintable ( * ch) ) ;
95+ pub fn verify_checksum ( s : & str ) -> Result < & str , Error > {
96+ let mut last_hash_pos = s. len ( ) ;
97+ for ( pos, ch) in s. char_indices ( ) {
98+ if !( 32 ..127 ) . contains ( & u32:: from ( ch) ) {
99+ return Err ( Error :: InvalidCharacter { ch, pos } ) ;
100+ } else if ch == '#' {
101+ last_hash_pos = pos;
42102 }
43103 }
104+ // After this point we know we have ASCII and can stop using character methods.
105+
106+ if last_hash_pos < s. len ( ) {
107+ let checksum_str = & s[ last_hash_pos + 1 ..] ;
108+ if checksum_str. len ( ) != CHECKSUM_LENGTH {
109+ return Err ( Error :: InvalidChecksumLength {
110+ actual : checksum_str. len ( ) ,
111+ expected : CHECKSUM_LENGTH ,
112+ } ) ;
113+ }
114+
115+ let mut eng = Engine :: new ( ) ;
116+ eng. input_unchecked ( s[ ..last_hash_pos] . as_bytes ( ) ) ;
44117
45- let mut parts = s. splitn ( 2 , '#' ) ;
46- let desc_str = parts. next ( ) . unwrap ( ) ;
47- if let Some ( checksum_str) = parts. next ( ) {
48- let expected_sum = desc_checksum ( desc_str) ?;
49- if checksum_str != expected_sum {
50- return Err ( Error :: BadDescriptor ( format ! (
51- "Invalid checksum '{}', expected '{}'" ,
52- checksum_str, expected_sum
53- ) ) ) ;
118+ let expected = eng. checksum_chars ( ) ;
119+ let mut actual = [ '_' ; CHECKSUM_LENGTH ] ;
120+ for ( act, ch) in actual. iter_mut ( ) . zip ( checksum_str. chars ( ) ) {
121+ * act = ch;
122+ }
123+
124+ if expected != actual {
125+ return Err ( Error :: InvalidChecksum { actual, expected } ) ;
54126 }
55127 }
56- Ok ( desc_str )
128+ Ok ( & s [ ..last_hash_pos ] )
57129}
58130
59131/// An engine to compute a checksum from a string.
@@ -78,16 +150,18 @@ impl Engine {
78150 /// If this function returns an error, the `Engine` will be left in an indeterminate
79151 /// state! It is safe to continue feeding it data but the result will not be meaningful.
80152 pub fn input ( & mut self , s : & str ) -> Result < ( ) , Error > {
81- for ch in s. chars ( ) {
82- let pos = VALID_CHARS
83- . get ( ch as usize )
84- . ok_or_else ( || {
85- Error :: BadDescriptor ( format ! ( "Invalid character in checksum: '{}'" , ch) )
86- } ) ?
87- . ok_or_else ( || {
88- Error :: BadDescriptor ( format ! ( "Invalid character in checksum: '{}'" , ch) )
89- } ) ? as u64 ;
153+ for ( pos, ch) in s. char_indices ( ) {
154+ if !( 32 ..127 ) . contains ( & u32:: from ( ch) ) {
155+ return Err ( Error :: InvalidCharacter { ch, pos } ) ;
156+ }
157+ }
158+ self . input_unchecked ( s. as_bytes ( ) ) ;
159+ Ok ( ( ) )
160+ }
90161
162+ fn input_unchecked ( & mut self , s : & [ u8 ] ) {
163+ for ch in s {
164+ let pos = u64:: from ( CHAR_MAP [ usize:: from ( * ch) - 32 ] ) ;
91165 let fe = Fe32 :: try_from ( pos & 31 ) . expect ( "pos is valid because of the mask" ) ;
92166 self . inner . input_fe ( fe) ;
93167
@@ -100,7 +174,6 @@ impl Engine {
100174 self . clscount = 0 ;
101175 }
102176 }
103- Ok ( ( ) )
104177 }
105178
106179 /// Obtains the checksum characters of all the data thus-far fed to the
@@ -192,7 +265,9 @@ mod test {
192265
193266 macro_rules! check_expected {
194267 ( $desc: expr, $checksum: expr) => {
195- assert_eq!( desc_checksum( $desc) . unwrap( ) , $checksum) ;
268+ let mut eng = Engine :: new( ) ;
269+ eng. input_unchecked( $desc. as_bytes( ) ) ;
270+ assert_eq!( eng. checksum( ) , $checksum) ;
196271 } ;
197272 }
198273
@@ -229,8 +304,8 @@ mod test {
229304 let invalid_desc = format ! ( "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" , sparkle_heart) ;
230305
231306 assert_eq ! (
232- desc_checksum ( & invalid_desc) . err( ) . unwrap( ) . to_string( ) ,
233- format!( "Invalid descriptor: Invalid character in checksum: '{}'" , sparkle_heart)
307+ verify_checksum ( & invalid_desc) . err( ) . unwrap( ) . to_string( ) ,
308+ format!( "invalid character '{}' (position 85) " , sparkle_heart)
234309 ) ;
235310 }
236311
0 commit comments