elements_miniscript/descriptor/
checksum.rs1use core::fmt;
9use core::iter::FromIterator;
10
11use bitcoin_miniscript::expression::check_valid_chars;
12
13use crate::Error;
14
15const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
16const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
17
18fn poly_mod(mut c: u64, val: u64) -> u64 {
19 let c0 = c >> 35;
20
21 c = ((c & 0x7ffffffff) << 5) ^ val;
22 if c0 & 1 > 0 {
23 c ^= 0xf5dee51989
24 };
25 if c0 & 2 > 0 {
26 c ^= 0xa9fdca3312
27 };
28 if c0 & 4 > 0 {
29 c ^= 0x1bab10e32d
30 };
31 if c0 & 8 > 0 {
32 c ^= 0x3706b1677a
33 };
34 if c0 & 16 > 0 {
35 c ^= 0x644d626ffd
36 };
37
38 c
39}
40
41pub fn desc_checksum(desc: &str) -> Result<String, Error> {
46 let mut eng = Engine::new();
47 eng.input(desc)?;
48 Ok(eng.checksum())
49}
50
51pub(crate) fn verify_checksum(s: &str) -> Result<&str, Error> {
56 check_valid_chars(s)?;
57
58 let mut parts = s.splitn(2, '#');
59 let desc_str = parts.next().unwrap();
60 if let Some(checksum_str) = parts.next() {
61 let expected_sum = desc_checksum(desc_str)?;
62 if checksum_str != expected_sum {
63 return Err(Error::BadDescriptor(format!(
64 "Invalid checksum '{}', expected '{}'",
65 checksum_str, expected_sum
66 )));
67 }
68 }
69 Ok(desc_str)
70}
71
72pub struct Engine {
74 c: u64,
75 cls: u64,
76 clscount: u64,
77}
78
79impl Default for Engine {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl Engine {
86 pub fn new() -> Self {
88 Engine {
89 c: 1,
90 cls: 0,
91 clscount: 0,
92 }
93 }
94
95 pub fn input(&mut self, s: &str) -> Result<(), Error> {
100 for ch in s.chars() {
101 let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
102 Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
103 })? as u64;
104 self.c = poly_mod(self.c, pos & 31);
105 self.cls = self.cls * 3 + (pos >> 5);
106 self.clscount += 1;
107 if self.clscount == 3 {
108 self.c = poly_mod(self.c, self.cls);
109 self.cls = 0;
110 self.clscount = 0;
111 }
112 }
113 Ok(())
114 }
115
116 pub fn checksum_chars(&mut self) -> [char; 8] {
118 if self.clscount > 0 {
119 self.c = poly_mod(self.c, self.cls);
120 }
121 (0..8).for_each(|_| self.c = poly_mod(self.c, 0));
122 self.c ^= 1;
123
124 let mut chars = [0 as char; 8];
125 for j in 0..8 {
126 chars[j] = CHECKSUM_CHARSET[((self.c >> (5 * (7 - j))) & 31) as usize] as char;
127 }
128 chars
129 }
130
131 pub fn checksum(&mut self) -> String {
133 String::from_iter(self.checksum_chars().iter().copied())
134 }
135}
136
137pub struct Formatter<'f, 'a> {
139 fmt: &'f mut fmt::Formatter<'a>,
140 eng: Engine,
141}
142
143impl<'f, 'a> Formatter<'f, 'a> {
144 pub fn new(f: &'f mut fmt::Formatter<'a>) -> Self {
146 Formatter {
147 fmt: f,
148 eng: Engine::new(),
149 }
150 }
151
152 pub fn write_checksum(&mut self) -> fmt::Result {
154 use fmt::Write;
155 self.fmt.write_char('#')?;
156 for ch in self.eng.checksum_chars().iter().copied() {
157 self.fmt.write_char(ch)?;
158 }
159 Ok(())
160 }
161
162 pub fn write_checksum_if_not_alt(&mut self) -> fmt::Result {
164 if !self.fmt.alternate() {
165 self.write_checksum()?;
166 }
167 Ok(())
168 }
169}
170
171impl<'f, 'a> fmt::Write for Formatter<'f, 'a> {
172 fn write_str(&mut self, s: &str) -> fmt::Result {
173 self.fmt.write_str(s)?;
174 self.eng.input(s).map_err(|_| fmt::Error)
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use std::str;
181
182 use super::*;
183
184 macro_rules! check_expected {
185 ($desc: expr, $checksum: expr) => {
186 assert_eq!(desc_checksum($desc).unwrap(), $checksum);
187 };
188 }
189
190 #[test]
191 fn test_valid_descriptor_checksum() {
192 check_expected!(
193 "elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)",
194 "hkvr2vkj"
195 );
196 check_expected!(
197 "elpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)",
198 "g7zpd3we"
199 );
200
201 check_expected!(
203 "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))",
204 "9s2ngs7u"
205 );
206 check_expected!(
207 "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))",
208 "uklept69"
209 );
210 }
211
212 #[test]
213 fn test_desc_checksum_invalid_character() {
214 let sparkle_heart = vec![240, 159, 146, 150];
215 let sparkle_heart = str::from_utf8(&sparkle_heart)
216 .unwrap()
217 .chars()
218 .next()
219 .unwrap();
220 let invalid_desc = format!("elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
221
222 assert_eq!(
223 desc_checksum(&invalid_desc).err().unwrap().to_string(),
224 format!(
225 "Invalid descriptor: Invalid character in checksum: '{}'",
226 sparkle_heart
227 )
228 );
229 }
230}