@@ -16,44 +16,116 @@ use bech32::{Checksum, Fe32};
16
16
17
17
pub use crate :: expression:: VALID_CHARS ;
18
18
use crate :: prelude:: * ;
19
- use crate :: Error ;
20
19
21
20
const CHECKSUM_LENGTH : usize = 8 ;
22
21
const CODE_LENGTH : usize = 32767 ;
23
22
24
- /// Compute the checksum of a descriptor.
23
+ /// Map of valid characters in descriptor strings .
25
24
///
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 }
32
89
}
33
90
34
91
/// Helper function for `FromStr` for various descriptor types.
35
92
///
36
93
/// Checks and verifies the checksum if it is present and returns the descriptor
37
94
/// 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;
42
102
}
43
103
}
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 ( ) ) ;
44
117
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 } ) ;
54
126
}
55
127
}
56
- Ok ( desc_str )
128
+ Ok ( & s [ ..last_hash_pos ] )
57
129
}
58
130
59
131
/// An engine to compute a checksum from a string.
@@ -78,16 +150,18 @@ impl Engine {
78
150
/// If this function returns an error, the `Engine` will be left in an indeterminate
79
151
/// state! It is safe to continue feeding it data but the result will not be meaningful.
80
152
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
+ }
90
161
162
+ fn input_unchecked ( & mut self , s : & [ u8 ] ) {
163
+ for ch in s {
164
+ let pos = u64:: from ( CHAR_MAP [ usize:: from ( * ch) - 32 ] ) ;
91
165
let fe = Fe32 :: try_from ( pos & 31 ) . expect ( "pos is valid because of the mask" ) ;
92
166
self . inner . input_fe ( fe) ;
93
167
@@ -100,7 +174,6 @@ impl Engine {
100
174
self . clscount = 0 ;
101
175
}
102
176
}
103
- Ok ( ( ) )
104
177
}
105
178
106
179
/// Obtains the checksum characters of all the data thus-far fed to the
@@ -192,7 +265,9 @@ mod test {
192
265
193
266
macro_rules! check_expected {
194
267
( $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) ;
196
271
} ;
197
272
}
198
273
@@ -229,8 +304,8 @@ mod test {
229
304
let invalid_desc = format ! ( "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)" , sparkle_heart) ;
230
305
231
306
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)
234
309
) ;
235
310
}
236
311
0 commit comments