Skip to content

Commit 72dab64

Browse files
committed
Merge #478: allow disabling the checksum with alternate Display
7f5bbdf fixes for 1.41.0 (Andrew Poelstra) 7577e8c checksum: use wrapper around fmt::Formatter for all descriptor types (Andrew Poelstra) 84f0292 checksum: pull computation apart into an engine (Andrew Poelstra) Pull request description: Fixes #477 ACKs for top commit: sanket1729: ACK 7f5bbdf. The nit is just a stylistic comment. Tree-SHA512: f4a5bd67bf15d6e59258ec42f0b046b8321103f1baa0e2f893d4e3379623c636f2092596c0c79a0d3b949a436307f052efca72ad2784e42dd23ad861e3036e85
2 parents d5615ac + 7f5bbdf commit 72dab64

File tree

6 files changed

+246
-87
lines changed

6 files changed

+246
-87
lines changed

src/descriptor/bare.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use core::fmt;
2323
use bitcoin::blockdata::script;
2424
use bitcoin::{Address, Network, Script};
2525

26-
use super::checksum::{desc_checksum, verify_checksum};
26+
use super::checksum::{self, verify_checksum};
2727
use crate::expression::{self, FromTree};
2828
use crate::miniscript::context::ScriptContext;
2929
use crate::policy::{semantic, Liftable};
@@ -132,9 +132,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {
132132

133133
impl<Pk: MiniscriptKey> fmt::Display for Bare<Pk> {
134134
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135-
let desc = format!("{}", self.ms);
136-
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
137-
write!(f, "{}#{}", &desc, &checksum)
135+
use fmt::Write;
136+
let mut wrapped_f = checksum::Formatter::new(f);
137+
write!(wrapped_f, "{}", self.ms)?;
138+
wrapped_f.write_checksum_if_not_alt()
138139
}
139140
}
140141

@@ -285,9 +286,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {
285286

286287
impl<Pk: MiniscriptKey> fmt::Display for Pkh<Pk> {
287288
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288-
let desc = format!("pkh({})", self.pk);
289-
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
290-
write!(f, "{}#{}", &desc, &checksum)
289+
use fmt::Write;
290+
let mut wrapped_f = checksum::Formatter::new(f);
291+
write!(wrapped_f, "pkh({})", self.pk)?;
292+
wrapped_f.write_checksum_if_not_alt()
291293
}
292294
}
293295

src/descriptor/checksum.rs

+110-35
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
44
//! checksum of a descriptor
55
6+
use core::fmt;
67
use core::iter::FromIterator;
78

89
use crate::prelude::*;
910
use crate::Error;
1011

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

1415
fn poly_mod(mut c: u64, val: u64) -> u64 {
1516
let c0 = c >> 35;
@@ -39,40 +40,9 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
3940
/// descriptor string is syntactically correct or not.
4041
/// This only computes the checksum
4142
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))
43+
let mut eng = Engine::new();
44+
eng.input(desc)?;
45+
Ok(eng.checksum())
7646
}
7747

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

src/descriptor/mod.rs

+92-12
Original file line numberDiff line numberDiff line change
@@ -808,25 +808,25 @@ impl_from_str!(
808808
impl<Pk: MiniscriptKey> fmt::Debug for Descriptor<Pk> {
809809
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
810810
match *self {
811-
Descriptor::Bare(ref sub) => write!(f, "{:?}", sub),
812-
Descriptor::Pkh(ref pkh) => write!(f, "{:?}", pkh),
813-
Descriptor::Wpkh(ref wpkh) => write!(f, "{:?}", wpkh),
814-
Descriptor::Sh(ref sub) => write!(f, "{:?}", sub),
815-
Descriptor::Wsh(ref sub) => write!(f, "{:?}", sub),
816-
Descriptor::Tr(ref tr) => write!(f, "{:?}", tr),
811+
Descriptor::Bare(ref sub) => fmt::Debug::fmt(sub, f),
812+
Descriptor::Pkh(ref pkh) => fmt::Debug::fmt(pkh, f),
813+
Descriptor::Wpkh(ref wpkh) => fmt::Debug::fmt(wpkh, f),
814+
Descriptor::Sh(ref sub) => fmt::Debug::fmt(sub, f),
815+
Descriptor::Wsh(ref sub) => fmt::Debug::fmt(sub, f),
816+
Descriptor::Tr(ref tr) => fmt::Debug::fmt(tr, f),
817817
}
818818
}
819819
}
820820

821821
impl<Pk: MiniscriptKey> fmt::Display for Descriptor<Pk> {
822822
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
823823
match *self {
824-
Descriptor::Bare(ref sub) => write!(f, "{}", sub),
825-
Descriptor::Pkh(ref pkh) => write!(f, "{}", pkh),
826-
Descriptor::Wpkh(ref wpkh) => write!(f, "{}", wpkh),
827-
Descriptor::Sh(ref sub) => write!(f, "{}", sub),
828-
Descriptor::Wsh(ref sub) => write!(f, "{}", sub),
829-
Descriptor::Tr(ref tr) => write!(f, "{}", tr),
824+
Descriptor::Bare(ref sub) => fmt::Display::fmt(sub, f),
825+
Descriptor::Pkh(ref pkh) => fmt::Display::fmt(pkh, f),
826+
Descriptor::Wpkh(ref wpkh) => fmt::Display::fmt(wpkh, f),
827+
Descriptor::Sh(ref sub) => fmt::Display::fmt(sub, f),
828+
Descriptor::Wsh(ref sub) => fmt::Display::fmt(sub, f),
829+
Descriptor::Tr(ref tr) => fmt::Display::fmt(tr, f),
830830
}
831831
}
832832
}
@@ -1757,4 +1757,84 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
17571757
Ok(Some((1, expected_concrete)))
17581758
);
17591759
}
1760+
1761+
#[test]
1762+
fn display_alternate() {
1763+
let bare = StdDescriptor::from_str(
1764+
"pk(020000000000000000000000000000000000000000000000000000000000000002)",
1765+
)
1766+
.unwrap();
1767+
assert_eq!(
1768+
format!("{}", bare),
1769+
"pk(020000000000000000000000000000000000000000000000000000000000000002)#7yxkn84h",
1770+
);
1771+
assert_eq!(
1772+
format!("{:#}", bare),
1773+
"pk(020000000000000000000000000000000000000000000000000000000000000002)",
1774+
);
1775+
1776+
let pkh = StdDescriptor::from_str(
1777+
"pkh(020000000000000000000000000000000000000000000000000000000000000002)",
1778+
)
1779+
.unwrap();
1780+
assert_eq!(
1781+
format!("{}", pkh),
1782+
"pkh(020000000000000000000000000000000000000000000000000000000000000002)#ma7nspkf",
1783+
);
1784+
assert_eq!(
1785+
format!("{:#}", pkh),
1786+
"pkh(020000000000000000000000000000000000000000000000000000000000000002)",
1787+
);
1788+
1789+
let wpkh = StdDescriptor::from_str(
1790+
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)",
1791+
)
1792+
.unwrap();
1793+
assert_eq!(
1794+
format!("{}", wpkh),
1795+
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)#d3xz2xye",
1796+
);
1797+
assert_eq!(
1798+
format!("{:#}", wpkh),
1799+
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)",
1800+
);
1801+
1802+
let shwpkh = StdDescriptor::from_str(
1803+
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))",
1804+
)
1805+
.unwrap();
1806+
assert_eq!(
1807+
format!("{}", shwpkh),
1808+
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))#45zpjtet",
1809+
);
1810+
assert_eq!(
1811+
format!("{:#}", shwpkh),
1812+
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))",
1813+
);
1814+
1815+
let wsh = StdDescriptor::from_str("wsh(1)").unwrap();
1816+
assert_eq!(format!("{}", wsh), "wsh(1)#mrg7xj7p");
1817+
assert_eq!(format!("{:#}", wsh), "wsh(1)");
1818+
1819+
let sh = StdDescriptor::from_str("sh(1)").unwrap();
1820+
assert_eq!(format!("{}", sh), "sh(1)#l8r75ggs");
1821+
assert_eq!(format!("{:#}", sh), "sh(1)");
1822+
1823+
let shwsh = StdDescriptor::from_str("sh(wsh(1))").unwrap();
1824+
assert_eq!(format!("{}", shwsh), "sh(wsh(1))#hcyfl07f");
1825+
assert_eq!(format!("{:#}", shwsh), "sh(wsh(1))");
1826+
1827+
let tr = StdDescriptor::from_str(
1828+
"tr(020000000000000000000000000000000000000000000000000000000000000002)",
1829+
)
1830+
.unwrap();
1831+
assert_eq!(
1832+
format!("{}", tr),
1833+
"tr(020000000000000000000000000000000000000000000000000000000000000002)#8hc7wq5h",
1834+
);
1835+
assert_eq!(
1836+
format!("{:#}", tr),
1837+
"tr(020000000000000000000000000000000000000000000000000000000000000002)",
1838+
);
1839+
}
17601840
}

src/descriptor/segwitv0.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use core::fmt;
2020

2121
use bitcoin::{self, Address, Network, Script};
2222

23-
use super::checksum::{desc_checksum, verify_checksum};
23+
use super::checksum::{self, verify_checksum};
2424
use super::SortedMultiVec;
2525
use crate::expression::{self, FromTree};
2626
use crate::miniscript::context::{ScriptContext, ScriptContextError};
@@ -68,11 +68,9 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
6868
}
6969

7070
/// Get the descriptor without the checksum
71+
#[deprecated(since = "8.0.0", note = "use format!(\"{:#}\") instead")]
7172
pub fn to_string_no_checksum(&self) -> String {
72-
match self.inner {
73-
WshInner::SortedMulti(ref smv) => format!("wsh({})", smv),
74-
WshInner::Ms(ref ms) => format!("wsh({})", ms),
75-
}
73+
format!("{:#}", self)
7674
}
7775

7876
/// Checks whether the descriptor is safe.
@@ -229,9 +227,13 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wsh<Pk> {
229227

230228
impl<Pk: MiniscriptKey> fmt::Display for Wsh<Pk> {
231229
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232-
let desc = self.to_string_no_checksum();
233-
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
234-
write!(f, "{}#{}", &desc, &checksum)
230+
use fmt::Write;
231+
let mut wrapped_f = checksum::Formatter::new(f);
232+
match self.inner {
233+
WshInner::SortedMulti(ref smv) => write!(wrapped_f, "wsh({})", smv)?,
234+
WshInner::Ms(ref ms) => write!(wrapped_f, "wsh({})", ms)?,
235+
}
236+
wrapped_f.write_checksum_if_not_alt()
235237
}
236238
}
237239

@@ -307,8 +309,9 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
307309
}
308310

309311
/// Get the descriptor without the checksum
312+
#[deprecated(since = "8.0.0", note = "use format!(\"{:#}\") instead")]
310313
pub fn to_string_no_checksum(&self) -> String {
311-
format!("wpkh({})", self.pk)
314+
format!("{:#}", self)
312315
}
313316

314317
/// Checks whether the descriptor is safe.
@@ -398,9 +401,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {
398401

399402
impl<Pk: MiniscriptKey> fmt::Display for Wpkh<Pk> {
400403
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
401-
let desc = self.to_string_no_checksum();
402-
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
403-
write!(f, "{}#{}", &desc, &checksum)
404+
use fmt::Write;
405+
let mut wrapped_f = checksum::Formatter::new(f);
406+
write!(wrapped_f, "wpkh({})", self.pk)?;
407+
wrapped_f.write_checksum_if_not_alt()
404408
}
405409
}
406410

0 commit comments

Comments
 (0)