3
3
//! Descriptor checksum
4
4
//!
5
5
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
6
- //! checksum of a descriptor
6
+ //! checksum of a descriptor. The checksum algorithm is specified in [BIP-380].
7
+ //!
8
+ //! [BIP-380]: <https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki>
7
9
8
10
use core:: fmt;
9
11
use core:: iter:: FromIterator ;
@@ -14,6 +16,8 @@ use crate::Error;
14
16
15
17
const CHECKSUM_CHARSET : & [ u8 ] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l" ;
16
18
19
+ const CHECKSUM_LENGTH : usize = 8 ;
20
+
17
21
fn poly_mod ( mut c : u64 , val : u64 ) -> u64 {
18
22
let c0 = c >> 35 ;
19
23
@@ -37,20 +41,20 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
37
41
c
38
42
}
39
43
40
- /// Compute the checksum of a descriptor
41
- /// Note that this function does not check if the
42
- /// descriptor string is syntactically correct or not.
43
- /// This only computes the checksum
44
+ /// Compute the checksum of a descriptor.
45
+ ///
46
+ /// Note that this function does not check if the descriptor string is
47
+ /// syntactically correct or not. This only computes the checksum.
44
48
pub fn desc_checksum ( desc : & str ) -> Result < String , Error > {
45
49
let mut eng = Engine :: new ( ) ;
46
50
eng. input ( desc) ?;
47
51
Ok ( eng. checksum ( ) )
48
52
}
49
53
50
- /// Helper function for FromStr for various
51
- /// descriptor types. Checks and verifies the checksum
52
- /// if it is present and returns the descriptor string
53
- /// without the checksum
54
+ /// Helper function for ` FromStr` for various descriptor types.
55
+ ///
56
+ /// Checks and verifies the checksum if it is present and returns the descriptor
57
+ /// string without the checksum.
54
58
pub ( super ) fn verify_checksum ( s : & str ) -> Result < & str , Error > {
55
59
for ch in s. as_bytes ( ) {
56
60
if * ch < 20 || * ch > 127 {
@@ -72,7 +76,7 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> {
72
76
Ok ( desc_str)
73
77
}
74
78
75
- /// An engine to compute a checksum from a string
79
+ /// An engine to compute a checksum from a string.
76
80
pub struct Engine {
77
81
c : u64 ,
78
82
cls : u64 ,
@@ -84,10 +88,10 @@ impl Default for Engine {
84
88
}
85
89
86
90
impl Engine {
87
- /// Construct an engine with no input
91
+ /// Constructs an engine with no input.
88
92
pub fn new ( ) -> Self { Engine { c : 1 , cls : 0 , clscount : 0 } }
89
93
90
- /// Checksum some data
94
+ /// Inputs some data into the checksum engine.
91
95
///
92
96
/// If this function returns an error, the `Engine` will be left in an indeterminate
93
97
/// state! It is safe to continue feeding it data but the result will not be meaningful.
@@ -113,38 +117,39 @@ impl Engine {
113
117
Ok ( ( ) )
114
118
}
115
119
116
- /// Obtain the checksum of all the data thus-far fed to the engine
117
- pub fn checksum_chars ( & mut self ) -> [ char ; 8 ] {
120
+ /// Obtains the checksum characters of all the data thus-far fed to the
121
+ /// engine without allocating, to get a string use [`Self::checksum`].
122
+ pub fn checksum_chars ( & mut self ) -> [ char ; CHECKSUM_LENGTH ] {
118
123
if self . clscount > 0 {
119
124
self . c = poly_mod ( self . c , self . cls ) ;
120
125
}
121
- ( 0 ..8 ) . for_each ( |_| self . c = poly_mod ( self . c , 0 ) ) ;
126
+ ( 0 ..CHECKSUM_LENGTH ) . for_each ( |_| self . c = poly_mod ( self . c , 0 ) ) ;
122
127
self . c ^= 1 ;
123
128
124
- let mut chars = [ 0 as char ; 8 ] ;
125
- for j in 0 ..8 {
129
+ let mut chars = [ 0 as char ; CHECKSUM_LENGTH ] ;
130
+ for j in 0 ..CHECKSUM_LENGTH {
126
131
chars[ j] = CHECKSUM_CHARSET [ ( ( self . c >> ( 5 * ( 7 - j) ) ) & 31 ) as usize ] as char ;
127
132
}
128
133
chars
129
134
}
130
135
131
- /// Obtain the checksum of all the data thus-far fed to the engine
136
+ /// Obtains the checksum of all the data thus-far fed to the engine.
132
137
pub fn checksum ( & mut self ) -> String {
133
138
String :: from_iter ( self . checksum_chars ( ) . iter ( ) . copied ( ) )
134
139
}
135
140
}
136
141
137
- /// A wrapper around a `fmt::Formatter` which provides checksumming ability
142
+ /// A wrapper around a `fmt::Formatter` which provides checksumming ability.
138
143
pub struct Formatter < ' f , ' a > {
139
144
fmt : & ' f mut fmt:: Formatter < ' a > ,
140
145
eng : Engine ,
141
146
}
142
147
143
148
impl < ' f , ' a > Formatter < ' f , ' a > {
144
- /// Contruct a new `Formatter`, wrapping a given `fmt::Formatter`
149
+ /// Contructs a new `Formatter`, wrapping a given `fmt::Formatter`.
145
150
pub fn new ( f : & ' f mut fmt:: Formatter < ' a > ) -> Self { Formatter { fmt : f, eng : Engine :: new ( ) } }
146
151
147
- /// Writes the checksum into the underlying `fmt::Formatter`
152
+ /// Writes the checksum into the underlying `fmt::Formatter`.
148
153
pub fn write_checksum ( & mut self ) -> fmt:: Result {
149
154
use fmt:: Write ;
150
155
self . fmt . write_char ( '#' ) ?;
@@ -154,7 +159,7 @@ impl<'f, 'a> Formatter<'f, 'a> {
154
159
Ok ( ( ) )
155
160
}
156
161
157
- /// Writes the checksum into the underlying `fmt::Formatter`, unless it has "alternate" display on
162
+ /// Writes the checksum into the underlying `fmt::Formatter`, unless it has "alternate" display on.
158
163
pub fn write_checksum_if_not_alt ( & mut self ) -> fmt:: Result {
159
164
if !self . fmt . alternate ( ) {
160
165
self . write_checksum ( ) ?;
@@ -219,4 +224,34 @@ mod test {
219
224
format!( "Invalid descriptor: Invalid character in checksum: '{}'" , sparkle_heart)
220
225
) ;
221
226
}
227
+
228
+ #[ test]
229
+ fn bip_380_test_vectors_checksum_and_character_set_valid ( ) {
230
+ let tcs = vec ! [
231
+ "raw(deadbeef)#89f8spxm" , // Valid checksum.
232
+ "raw(deadbeef)" , // No checksum.
233
+ ] ;
234
+ for tc in tcs {
235
+ if verify_checksum ( tc) . is_err ( ) {
236
+ panic ! ( "false negative: {}" , tc)
237
+ }
238
+ }
239
+ }
240
+
241
+ #[ test]
242
+ fn bip_380_test_vectors_checksum_and_character_set_invalid ( ) {
243
+ let tcs = vec ! [
244
+ "raw(deadbeef)#" , // Missing checksum.
245
+ "raw(deadbeef)#89f8spxmx" , // Too long checksum.
246
+ "raw(deadbeef)#89f8spx" , // Too short checksum.
247
+ "raw(dedbeef)#89f8spxm" , // Error in payload.
248
+ "raw(deadbeef)##9f8spxm" , // Error in checksum.
249
+ "raw(Ü)#00000000" , // Invalid characters in payload.
250
+ ] ;
251
+ for tc in tcs {
252
+ if verify_checksum ( tc) . is_ok ( ) {
253
+ panic ! ( "false positive: {}" , tc)
254
+ }
255
+ }
256
+ }
222
257
}
0 commit comments