@@ -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