Skip to content

Commit 29b612c

Browse files
committed
Merge #608: Use bech32 to calculate descriptor checksums
fe1a6be Implement write_descriptor macro (Tobin C. Harding) e9eb9b1 Use bech32 instead of custom poly_mod function (Tobin C. Harding) Pull request description: This is just the last patch, the rest are in #609 now. The `checksum::poly_mod` function implements BCH codes to calculate a checksum (appended to descriptors). We recently released a version of BCH codes in `bech32`. We can implement the `bech32::Checksum` trait for BIP-380 and use the `primitives::checksum` module, removing the custom `poly_mod` function. ACKs for top commit: apoelstra: ACK fe1a6be Tree-SHA512: 7be33951669982ce7b7d2e32197e5eb9c40323d31785a6ed0e4d47c6200c7cb0d4aadbce54cf112511dd680cc09a33f0e580aa193e71164ceaeacfecd98a1b19
2 parents 1ed13a9 + fe1a6be commit 29b612c

File tree

6 files changed

+73
-67
lines changed

6 files changed

+73
-67
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ edition = "2018"
1212

1313
[features]
1414
default = ["std"]
15-
std = ["bitcoin/std", "bitcoin/secp-recovery"]
16-
no-std = ["bitcoin/no-std"]
15+
std = ["bitcoin/std", "bitcoin/secp-recovery", "bech32/std"]
16+
no-std = ["bitcoin/no-std", "bech32/alloc"]
1717
compiler = []
1818
trace = []
1919

@@ -22,6 +22,7 @@ rand = ["bitcoin/rand"]
2222
base64 = ["bitcoin/base64"]
2323

2424
[dependencies]
25+
bech32 = { version = "0.10.0-beta", default-features = false }
2526
bitcoin = { version = "0.30.0", default-features = false }
2627
internals = { package = "bitcoin-private", version = "0.1.0", default_features = false }
2728

src/descriptor/bare.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use core::fmt;
1212
use bitcoin::script::{self, PushBytes};
1313
use bitcoin::{Address, Network, ScriptBuf};
1414

15-
use super::checksum::{self, verify_checksum};
16-
use crate::descriptor::DefiniteDescriptorKey;
15+
use super::checksum::verify_checksum;
16+
use crate::descriptor::{write_descriptor, DefiniteDescriptorKey};
1717
use crate::expression::{self, FromTree};
1818
use crate::miniscript::context::{ScriptContext, ScriptContextError};
1919
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
@@ -156,12 +156,7 @@ impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {
156156
}
157157

158158
impl<Pk: MiniscriptKey> fmt::Display for Bare<Pk> {
159-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160-
use fmt::Write;
161-
let mut wrapped_f = checksum::Formatter::new(f);
162-
write!(wrapped_f, "{}", self.ms)?;
163-
wrapped_f.write_checksum_if_not_alt()
164-
}
159+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write_descriptor!(f, "{}", self.ms) }
165160
}
166161

167162
impl<Pk: MiniscriptKey> Liftable<Pk> for Bare<Pk> {
@@ -354,10 +349,7 @@ impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {
354349

355350
impl<Pk: MiniscriptKey> fmt::Display for Pkh<Pk> {
356351
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
357-
use fmt::Write;
358-
let mut wrapped_f = checksum::Formatter::new(f);
359-
write!(wrapped_f, "pkh({})", self.pk)?;
360-
wrapped_f.write_checksum_if_not_alt()
352+
write_descriptor!(f, "pkh({})", self.pk)
361353
}
362354
}
363355

src/descriptor/checksum.rs

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,19 @@
77
//!
88
//! [BIP-380]: <https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki>
99
10+
use core::convert::TryFrom;
1011
use core::fmt;
1112
use core::iter::FromIterator;
1213

14+
use bech32::primitives::checksum::PackedFe32;
15+
use bech32::{Checksum, Fe32};
16+
1317
pub use crate::expression::VALID_CHARS;
1418
use crate::prelude::*;
1519
use crate::Error;
1620

17-
const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
18-
1921
const CHECKSUM_LENGTH: usize = 8;
2022

21-
fn poly_mod(mut c: u64, val: u64) -> u64 {
22-
let c0 = c >> 35;
23-
24-
c = ((c & 0x7ffffffff) << 5) ^ val;
25-
if c0 & 1 > 0 {
26-
c ^= 0xf5dee51989
27-
};
28-
if c0 & 2 > 0 {
29-
c ^= 0xa9fdca3312
30-
};
31-
if c0 & 4 > 0 {
32-
c ^= 0x1bab10e32d
33-
};
34-
if c0 & 8 > 0 {
35-
c ^= 0x3706b1677a
36-
};
37-
if c0 & 16 > 0 {
38-
c ^= 0x644d626ffd
39-
};
40-
41-
c
42-
}
43-
4423
/// Compute the checksum of a descriptor.
4524
///
4625
/// Note that this function does not check if the descriptor string is
@@ -78,7 +57,7 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> {
7857

7958
/// An engine to compute a checksum from a string.
8059
pub struct Engine {
81-
c: u64,
60+
inner: bech32::primitives::checksum::Engine<DescriptorChecksum>,
8261
cls: u64,
8362
clscount: u64,
8463
}
@@ -89,7 +68,9 @@ impl Default for Engine {
8968

9069
impl Engine {
9170
/// Constructs an engine with no input.
92-
pub fn new() -> Self { Engine { c: 1, cls: 0, clscount: 0 } }
71+
pub fn new() -> Self {
72+
Engine { inner: bech32::primitives::checksum::Engine::new(), cls: 0, clscount: 0 }
73+
}
9374

9475
/// Inputs some data into the checksum engine.
9576
///
@@ -105,11 +86,15 @@ impl Engine {
10586
.ok_or_else(|| {
10687
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
10788
})? as u64;
108-
self.c = poly_mod(self.c, pos & 31);
89+
90+
let fe = Fe32::try_from(pos & 31).expect("pos is valid because of the mask");
91+
self.inner.input_fe(fe);
92+
10993
self.cls = self.cls * 3 + (pos >> 5);
11094
self.clscount += 1;
11195
if self.clscount == 3 {
112-
self.c = poly_mod(self.c, self.cls);
96+
let fe = Fe32::try_from(self.cls).expect("cls is valid");
97+
self.inner.input_fe(fe);
11398
self.cls = 0;
11499
self.clscount = 0;
115100
}
@@ -121,14 +106,19 @@ impl Engine {
121106
/// engine without allocating, to get a string use [`Self::checksum`].
122107
pub fn checksum_chars(&mut self) -> [char; CHECKSUM_LENGTH] {
123108
if self.clscount > 0 {
124-
self.c = poly_mod(self.c, self.cls);
109+
let fe = Fe32::try_from(self.cls).expect("cls is valid");
110+
self.inner.input_fe(fe);
125111
}
126-
(0..CHECKSUM_LENGTH).for_each(|_| self.c = poly_mod(self.c, 0));
127-
self.c ^= 1;
112+
self.inner.input_target_residue();
128113

129114
let mut chars = [0 as char; CHECKSUM_LENGTH];
115+
let mut checksum_remaining = CHECKSUM_LENGTH;
116+
130117
for j in 0..CHECKSUM_LENGTH {
131-
chars[j] = CHECKSUM_CHARSET[((self.c >> (5 * (7 - j))) & 31) as usize] as char;
118+
checksum_remaining -= 1;
119+
let unpacked = self.inner.residue().unpack(checksum_remaining);
120+
let fe = Fe32::try_from(unpacked).expect("5 bits fits in an fe32");
121+
chars[j] = fe.to_char();
132122
}
133123
chars
134124
}
@@ -139,6 +129,23 @@ impl Engine {
139129
}
140130
}
141131

132+
/// The Output Script Descriptor checksum algorithm, defined in [BIP-380].
133+
///
134+
/// [BIP-380]: <https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki>
135+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
136+
enum DescriptorChecksum {}
137+
138+
/// Generator coefficients, taken from BIP-380.
139+
#[rustfmt::skip]
140+
const GEN: [u64; 5] = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd];
141+
142+
impl Checksum for DescriptorChecksum {
143+
type MidstateRepr = u64; // We need 40 bits (8 * 5).
144+
const CHECKSUM_LENGTH: usize = CHECKSUM_LENGTH;
145+
const GENERATOR_SH: [u64; 5] = GEN;
146+
const TARGET_RESIDUE: u64 = 1;
147+
}
148+
142149
/// A wrapper around a `fmt::Formatter` which provides checksumming ability.
143150
pub struct Formatter<'f, 'a> {
144151
fmt: &'f mut fmt::Formatter<'a>,

src/descriptor/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,21 @@ impl<Pk: MiniscriptKey> fmt::Display for Descriptor<Pk> {
978978

979979
serde_string_impl_pk!(Descriptor, "a script descriptor");
980980

981+
macro_rules! write_descriptor {
982+
($fmt:expr, $s:literal $(, $args:expr)*) => {
983+
{
984+
use fmt::Write as _;
985+
986+
let mut wrapped_f = $crate::descriptor::checksum::Formatter::new($fmt);
987+
write!(wrapped_f, $s $(, $args)*)?;
988+
wrapped_f.write_checksum_if_not_alt()?;
989+
990+
fmt::Result::Ok(())
991+
}
992+
}
993+
}
994+
pub(crate) use write_descriptor;
995+
981996
#[cfg(test)]
982997
mod tests {
983998
use core::convert::TryFrom;

src/descriptor/segwitv0.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use core::fmt;
99

1010
use bitcoin::{Address, Network, ScriptBuf};
1111

12-
use super::checksum::{self, verify_checksum};
12+
use super::checksum::verify_checksum;
1313
use super::SortedMultiVec;
14-
use crate::descriptor::DefiniteDescriptorKey;
14+
use crate::descriptor::{write_descriptor, DefiniteDescriptorKey};
1515
use crate::expression::{self, FromTree};
1616
use crate::miniscript::context::{ScriptContext, ScriptContextError};
1717
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
@@ -260,13 +260,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wsh<Pk> {
260260

261261
impl<Pk: MiniscriptKey> fmt::Display for Wsh<Pk> {
262262
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263-
use fmt::Write;
264-
let mut wrapped_f = checksum::Formatter::new(f);
265263
match self.inner {
266-
WshInner::SortedMulti(ref smv) => write!(wrapped_f, "wsh({})", smv)?,
267-
WshInner::Ms(ref ms) => write!(wrapped_f, "wsh({})", ms)?,
264+
WshInner::SortedMulti(ref smv) => write_descriptor!(f, "wsh({})", smv),
265+
WshInner::Ms(ref ms) => write_descriptor!(f, "wsh({})", ms),
268266
}
269-
wrapped_f.write_checksum_if_not_alt()
270267
}
271268
}
272269

@@ -461,10 +458,7 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {
461458

462459
impl<Pk: MiniscriptKey> fmt::Display for Wpkh<Pk> {
463460
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464-
use fmt::Write;
465-
let mut wrapped_f = checksum::Formatter::new(f);
466-
write!(wrapped_f, "wpkh({})", self.pk)?;
467-
wrapped_f.write_checksum_if_not_alt()
461+
write_descriptor!(f, "wpkh({})", self.pk)
468462
}
469463
}
470464

src/descriptor/sh.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use core::fmt;
1313
use bitcoin::script::PushBytes;
1414
use bitcoin::{script, Address, Network, ScriptBuf};
1515

16-
use super::checksum::{self, verify_checksum};
16+
use super::checksum::verify_checksum;
1717
use super::{SortedMultiVec, Wpkh, Wsh};
18-
use crate::descriptor::DefiniteDescriptorKey;
18+
use crate::descriptor::{write_descriptor, DefiniteDescriptorKey};
1919
use crate::expression::{self, FromTree};
2020
use crate::miniscript::context::ScriptContext;
2121
use crate::miniscript::satisfy::{Placeholder, Satisfaction};
@@ -72,15 +72,12 @@ impl<Pk: MiniscriptKey> fmt::Debug for Sh<Pk> {
7272

7373
impl<Pk: MiniscriptKey> fmt::Display for Sh<Pk> {
7474
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75-
use fmt::Write;
76-
let mut wrapped_f = checksum::Formatter::new(f);
7775
match self.inner {
78-
ShInner::Wsh(ref wsh) => write!(wrapped_f, "sh({:#})", wsh)?,
79-
ShInner::Wpkh(ref pk) => write!(wrapped_f, "sh({:#})", pk)?,
80-
ShInner::SortedMulti(ref smv) => write!(wrapped_f, "sh({})", smv)?,
81-
ShInner::Ms(ref ms) => write!(wrapped_f, "sh({})", ms)?,
76+
ShInner::Wsh(ref wsh) => write_descriptor!(f, "sh({:#})", wsh),
77+
ShInner::Wpkh(ref pk) => write_descriptor!(f, "sh({:#})", pk),
78+
ShInner::SortedMulti(ref smv) => write_descriptor!(f, "sh({})", smv),
79+
ShInner::Ms(ref ms) => write_descriptor!(f, "sh({})", ms),
8280
}
83-
wrapped_f.write_checksum_if_not_alt()
8481
}
8582
}
8683

0 commit comments

Comments
 (0)