33//! Descriptor checksum
44//!
55//! 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>
79
810use core:: fmt;
911use core:: iter:: FromIterator ;
@@ -14,6 +16,8 @@ use crate::Error;
1416
1517const CHECKSUM_CHARSET : & [ u8 ] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l" ;
1618
19+ const CHECKSUM_LENGTH : usize = 8 ;
20+
1721fn poly_mod ( mut c : u64 , val : u64 ) -> u64 {
1822 let c0 = c >> 35 ;
1923
@@ -37,20 +41,20 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
3741 c
3842}
3943
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.
4448pub fn desc_checksum ( desc : & str ) -> Result < String , Error > {
4549 let mut eng = Engine :: new ( ) ;
4650 eng. input ( desc) ?;
4751 Ok ( eng. checksum ( ) )
4852}
4953
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.
5458pub ( super ) fn verify_checksum ( s : & str ) -> Result < & str , Error > {
5559 for ch in s. as_bytes ( ) {
5660 if * ch < 20 || * ch > 127 {
@@ -72,7 +76,7 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> {
7276 Ok ( desc_str)
7377}
7478
75- /// An engine to compute a checksum from a string
79+ /// An engine to compute a checksum from a string.
7680pub struct Engine {
7781 c : u64 ,
7882 cls : u64 ,
@@ -84,10 +88,10 @@ impl Default for Engine {
8488}
8589
8690impl Engine {
87- /// Construct an engine with no input
91+ /// Constructs an engine with no input.
8892 pub fn new ( ) -> Self { Engine { c : 1 , cls : 0 , clscount : 0 } }
8993
90- /// Checksum some data
94+ /// Inputs some data into the checksum engine.
9195 ///
9296 /// If this function returns an error, the `Engine` will be left in an indeterminate
9397 /// state! It is safe to continue feeding it data but the result will not be meaningful.
@@ -113,38 +117,39 @@ impl Engine {
113117 Ok ( ( ) )
114118 }
115119
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 ] {
118123 if self . clscount > 0 {
119124 self . c = poly_mod ( self . c , self . cls ) ;
120125 }
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 ) ) ;
122127 self . c ^= 1 ;
123128
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 {
126131 chars[ j] = CHECKSUM_CHARSET [ ( ( self . c >> ( 5 * ( 7 - j) ) ) & 31 ) as usize ] as char ;
127132 }
128133 chars
129134 }
130135
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.
132137 pub fn checksum ( & mut self ) -> String {
133138 String :: from_iter ( self . checksum_chars ( ) . iter ( ) . copied ( ) )
134139 }
135140}
136141
137- /// A wrapper around a `fmt::Formatter` which provides checksumming ability
142+ /// A wrapper around a `fmt::Formatter` which provides checksumming ability.
138143pub struct Formatter < ' f , ' a > {
139144 fmt : & ' f mut fmt:: Formatter < ' a > ,
140145 eng : Engine ,
141146}
142147
143148impl < ' 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`.
145150 pub fn new ( f : & ' f mut fmt:: Formatter < ' a > ) -> Self { Formatter { fmt : f, eng : Engine :: new ( ) } }
146151
147- /// Writes the checksum into the underlying `fmt::Formatter`
152+ /// Writes the checksum into the underlying `fmt::Formatter`.
148153 pub fn write_checksum ( & mut self ) -> fmt:: Result {
149154 use fmt:: Write ;
150155 self . fmt . write_char ( '#' ) ?;
@@ -154,7 +159,7 @@ impl<'f, 'a> Formatter<'f, 'a> {
154159 Ok ( ( ) )
155160 }
156161
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.
158163 pub fn write_checksum_if_not_alt ( & mut self ) -> fmt:: Result {
159164 if !self . fmt . alternate ( ) {
160165 self . write_checksum ( ) ?;
@@ -219,4 +224,34 @@ mod test {
219224 format!( "Invalid descriptor: Invalid character in checksum: '{}'" , sparkle_heart)
220225 ) ;
221226 }
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+ }
222257}
0 commit comments