Skip to content

Commit e73a251

Browse files
committed
Merge #609: Do some checksum module improvements
5018673 Remove magic number (Tobin C. Harding) 535cd17 Improve docs in the checksum module (Tobin C. Harding) fec700a Add bip 380 checksum test vectors (Tobin C. Harding) 73f4892 Add output descriptor bip referenece (Tobin C. Harding) Pull request description: The first 4 patches from #608, no changes. Clean up and add some test vector unit tests. ACKs for top commit: apoelstra: ACK 5018673 Tree-SHA512: f50f64bf9a1fc28eebca809379e02580cab96e7e41228aab6045441eb71702bef1b1979e497a6dcb1e1bce082965e5c93e78dba6e8fbd78c7a0ae2e3c8035660
2 parents 1ebde2d + 5018673 commit e73a251

File tree

1 file changed

+57
-22
lines changed

1 file changed

+57
-22
lines changed

src/descriptor/checksum.rs

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
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
810
use core::fmt;
911
use core::iter::FromIterator;
@@ -14,6 +16,8 @@ use crate::Error;
1416

1517
const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
1618

19+
const CHECKSUM_LENGTH: usize = 8;
20+
1721
fn 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.
4448
pub 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.
5458
pub(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.
7680
pub struct Engine {
7781
c: u64,
7882
cls: u64,
@@ -84,10 +88,10 @@ impl Default for Engine {
8488
}
8589

8690
impl 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.
138143
pub struct Formatter<'f, 'a> {
139144
fmt: &'f mut fmt::Formatter<'a>,
140145
eng: Engine,
141146
}
142147

143148
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`.
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

Comments
 (0)