Skip to content

Commit e9eb9b1

Browse files
committed
Use bech32 instead of custom poly_mod function
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.
1 parent 1ed13a9 commit e9eb9b1

File tree

2 files changed

+43
-35
lines changed

2 files changed

+43
-35
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/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>,

0 commit comments

Comments
 (0)