Skip to content

Commit 84f0292

Browse files
committed
checksum: pull computation apart into an engine
This will let us avoid some allocations when formatting descriptors, and make it simpler to selectively append a checksum.
1 parent d5615ac commit 84f0292

File tree

1 file changed

+102
-35
lines changed

1 file changed

+102
-35
lines changed

src/descriptor/checksum.rs

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
44
//! checksum of a descriptor
55
6+
#![allow(dead_code)] // will be removed in next commit
7+
use core::fmt;
68
use core::iter::FromIterator;
79

810
use crate::prelude::*;
911
use crate::Error;
1012

1113
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
12-
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
14+
const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
1315

1416
fn poly_mod(mut c: u64, val: u64) -> u64 {
1517
let c0 = c >> 35;
@@ -39,40 +41,9 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
3941
/// descriptor string is syntactically correct or not.
4042
/// This only computes the checksum
4143
pub fn desc_checksum(desc: &str) -> Result<String, Error> {
42-
let mut c = 1;
43-
let mut cls = 0;
44-
let mut clscount = 0;
45-
46-
for ch in desc.chars() {
47-
let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
48-
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
49-
})? as u64;
50-
c = poly_mod(c, pos & 31);
51-
cls = cls * 3 + (pos >> 5);
52-
clscount += 1;
53-
if clscount == 3 {
54-
c = poly_mod(c, cls);
55-
cls = 0;
56-
clscount = 0;
57-
}
58-
}
59-
if clscount > 0 {
60-
c = poly_mod(c, cls);
61-
}
62-
(0..8).for_each(|_| c = poly_mod(c, 0));
63-
c ^= 1;
64-
65-
let mut chars = Vec::with_capacity(8);
66-
for j in 0..8 {
67-
chars.push(
68-
CHECKSUM_CHARSET
69-
.chars()
70-
.nth(((c >> (5 * (7 - j))) & 31) as usize)
71-
.unwrap(),
72-
);
73-
}
74-
75-
Ok(String::from_iter(chars))
44+
let mut eng = Engine::new();
45+
eng.input(desc)?;
46+
Ok(eng.checksum())
7647
}
7748

7849
/// Helper function for FromStr for various
@@ -99,6 +70,102 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> {
9970
}
10071
Ok(desc_str)
10172
}
73+
74+
/// An engine to compute a checksum from a string
75+
pub struct Engine {
76+
c: u64,
77+
cls: u64,
78+
clscount: u64,
79+
}
80+
81+
impl Engine {
82+
/// Construct an engine with no input
83+
pub fn new() -> Self {
84+
Engine {
85+
c: 1,
86+
cls: 0,
87+
clscount: 0,
88+
}
89+
}
90+
91+
/// Checksum some data
92+
///
93+
/// If this function returns an error, the `Engine` will be left in an indeterminate
94+
/// state! It is safe to continue feeding it data but the result will not be meaningful.
95+
pub fn input(&mut self, s: &str) -> Result<(), Error> {
96+
for ch in s.chars() {
97+
let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
98+
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
99+
})? as u64;
100+
self.c = poly_mod(self.c, pos & 31);
101+
self.cls = self.cls * 3 + (pos >> 5);
102+
self.clscount += 1;
103+
if self.clscount == 3 {
104+
self.c = poly_mod(self.c, self.cls);
105+
self.cls = 0;
106+
self.clscount = 0;
107+
}
108+
}
109+
Ok(())
110+
}
111+
112+
/// Obtain the checksum of all the data thus-far fed to the engine
113+
pub fn checksum_chars(&mut self) -> [char; 8] {
114+
if self.clscount > 0 {
115+
self.c = poly_mod(self.c, self.cls);
116+
}
117+
(0..8).for_each(|_| self.c = poly_mod(self.c, 0));
118+
self.c ^= 1;
119+
120+
let mut chars = [0 as char; 8];
121+
for j in 0..8 {
122+
chars[j] = CHECKSUM_CHARSET[((self.c >> (5 * (7 - j))) & 31) as usize] as char;
123+
}
124+
chars
125+
}
126+
127+
/// Obtain the checksum of all the data thus-far fed to the engine
128+
pub fn checksum(&mut self) -> String {
129+
String::from_iter(self.checksum_chars())
130+
}
131+
}
132+
133+
/// A wrapper around a `fmt::Formatter` which provides checksumming ability
134+
pub struct Formatter<'f, 'a> {
135+
fmt: &'f mut fmt::Formatter<'a>,
136+
eng: Engine,
137+
}
138+
139+
impl<'f, 'a> Formatter<'f, 'a> {
140+
/// Contruct a new `Formatter`, wrapping a given `fmt::Formatter`
141+
pub fn new(f: &'f mut fmt::Formatter<'a>) -> Self {
142+
Formatter {
143+
fmt: f,
144+
eng: Engine::new(),
145+
}
146+
}
147+
148+
pub fn write_checksum(&mut self) -> fmt::Result {
149+
use fmt::Write;
150+
self.fmt.write_char('#')?;
151+
for ch in self.eng.checksum_chars() {
152+
self.fmt.write_char(ch)?;
153+
}
154+
Ok(())
155+
}
156+
}
157+
158+
impl<'f, 'a> fmt::Write for Formatter<'f, 'a> {
159+
fn write_str(&mut self, s: &str) -> fmt::Result {
160+
self.fmt.write_str(s)?;
161+
if self.eng.input(s).is_ok() {
162+
Ok(())
163+
} else {
164+
Err(fmt::Error)
165+
}
166+
}
167+
}
168+
102169
#[cfg(test)]
103170
mod test {
104171
use core::str;

0 commit comments

Comments
 (0)