Skip to content

Commit b44431f

Browse files
committed
checksum: introduce PrintImpl object to print impl Checksum blocks
For the most part, BCH codes used in the ecosystem are defined as generator polynomials over GF32 and a target residue polynomial, and the remaining code parameters are not specified, or are only specified indirectly. It is difficult for non-experts to compute or even validate parameters such as the length of the code or the shifted+packed generator polynomials. It will be even more difficult in the sequel when we extend the Checksum trait to also cover error-correction parameters (some of which will depend on our particular choice of representation for GF1024 elements, which is not canonical and differs between different documents). So we introduce an object `PrintImpl` and a unit test and doctest demonstrating its use, which will generate all of the needed parameters and output a block of Rust code.
1 parent 65aefd2 commit b44431f

File tree

4 files changed

+257
-2
lines changed

4 files changed

+257
-2
lines changed

api/all-features.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,14 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::Invali
438438
impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::WitnessLengthError
439439
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError
440440
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError
441+
impl<'a, ExtField> bech32::primitives::checksum::PrintImpl<'a, ExtField>
442+
impl<'a, ExtField> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: bech32::primitives::ExtensionField + core::convert::From<bech32::primitives::gf32::Fe32>
443+
impl<'a, ExtField> core::marker::Freeze for bech32::primitives::checksum::PrintImpl<'a, ExtField>
444+
impl<'a, ExtField> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Send
445+
impl<'a, ExtField> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Sync
446+
impl<'a, ExtField> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Unpin
447+
impl<'a, ExtField> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::RefUnwindSafe
448+
impl<'a, ExtField> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::UnwindSafe
441449
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
442450
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::CharIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
443451
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b>
@@ -893,6 +901,8 @@ pub fn bech32::primitives::checksum::PackedNull::fmt(&self, f: &mut core::fmt::F
893901
pub fn bech32::primitives::checksum::PackedNull::mul_by_x_then_add(&mut self, usize, u8) -> u8
894902
pub fn bech32::primitives::checksum::PackedNull::pack<I: core::iter::traits::iterator::Iterator<Item = u8>>(iter: I) -> Self
895903
pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8
904+
pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
905+
pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::new(name: &'a str, generator: &'a [bech32::primitives::gf32::Fe32], target: &'a [bech32::primitives::gf32::Fe32]) -> Self
896906
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::len(&self) -> usize
897907
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::next(&mut self) -> core::option::Option<bech32::primitives::gf32::Fe32>
898908
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option<usize>)
@@ -1194,10 +1204,12 @@ pub mod bech32::primitives::iter
11941204
pub mod bech32::primitives::segwit
11951205
pub mod bech32::segwit
11961206
pub struct bech32::Hrp
1207+
pub struct bech32::PrintImpl<'a, ExtField>
11971208
pub struct bech32::hrp::Hrp
11981209
pub struct bech32::primitives::checksum::Engine<Ck: bech32::primitives::checksum::Checksum>
11991210
pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp>
12001211
pub struct bech32::primitives::checksum::PackedNull
1212+
pub struct bech32::primitives::checksum::PrintImpl<'a, ExtField>
12011213
pub struct bech32::primitives::decode::AsciiToFe32Iter<'s>
12021214
pub struct bech32::primitives::decode::ByteIter<'s>
12031215
pub struct bech32::primitives::decode::CheckedHrpstring<'s>

api/alloc-only.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,14 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::Invali
409409
impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::WitnessLengthError
410410
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError
411411
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError
412+
impl<'a, ExtField> bech32::primitives::checksum::PrintImpl<'a, ExtField>
413+
impl<'a, ExtField> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: bech32::primitives::ExtensionField + core::convert::From<bech32::primitives::gf32::Fe32>
414+
impl<'a, ExtField> core::marker::Freeze for bech32::primitives::checksum::PrintImpl<'a, ExtField>
415+
impl<'a, ExtField> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Send
416+
impl<'a, ExtField> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Sync
417+
impl<'a, ExtField> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Unpin
418+
impl<'a, ExtField> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::RefUnwindSafe
419+
impl<'a, ExtField> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::UnwindSafe
412420
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
413421
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::CharIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
414422
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b>
@@ -853,6 +861,8 @@ pub fn bech32::primitives::checksum::PackedNull::fmt(&self, f: &mut core::fmt::F
853861
pub fn bech32::primitives::checksum::PackedNull::mul_by_x_then_add(&mut self, usize, u8) -> u8
854862
pub fn bech32::primitives::checksum::PackedNull::pack<I: core::iter::traits::iterator::Iterator<Item = u8>>(iter: I) -> Self
855863
pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8
864+
pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
865+
pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::new(name: &'a str, generator: &'a [bech32::primitives::gf32::Fe32], target: &'a [bech32::primitives::gf32::Fe32]) -> Self
856866
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::len(&self) -> usize
857867
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::next(&mut self) -> core::option::Option<bech32::primitives::gf32::Fe32>
858868
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option<usize>)
@@ -1136,10 +1146,12 @@ pub mod bech32::primitives::iter
11361146
pub mod bech32::primitives::segwit
11371147
pub mod bech32::segwit
11381148
pub struct bech32::Hrp
1149+
pub struct bech32::PrintImpl<'a, ExtField>
11391150
pub struct bech32::hrp::Hrp
11401151
pub struct bech32::primitives::checksum::Engine<Ck: bech32::primitives::checksum::Checksum>
11411152
pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp>
11421153
pub struct bech32::primitives::checksum::PackedNull
1154+
pub struct bech32::primitives::checksum::PrintImpl<'a, ExtField>
11431155
pub struct bech32::primitives::decode::AsciiToFe32Iter<'s>
11441156
pub struct bech32::primitives::decode::ByteIter<'s>
11451157
pub struct bech32::primitives::decode::CheckedHrpstring<'s>

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ pub use {
166166
crate::primitives::iter::{ByteIterExt, Fe32IterExt},
167167
crate::primitives::{Bech32, Bech32m, NoChecksum},
168168
};
169+
#[cfg(feature = "alloc")]
170+
pub use crate::primitives::checksum::PrintImpl;
169171

170172
// Write to fmt buffer, small during testing to exercise full code path.
171173
#[cfg(not(test))]

src/primitives/checksum.rs

Lines changed: 231 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@
44
//!
55
//! [BCH]: <https://en.wikipedia.org/wiki/BCH_code>
66
7+
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
8+
use alloc::vec::Vec;
9+
#[cfg(feature = "alloc")]
10+
use core::fmt;
11+
#[cfg(feature = "alloc")]
12+
use core::marker::PhantomData;
713
use core::{mem, ops};
814

9-
use crate::primitives::gf32::Fe32;
15+
#[cfg(feature = "alloc")]
16+
use super::Polynomial;
1017
use crate::primitives::hrp::Hrp;
18+
#[cfg(feature = "alloc")]
19+
use crate::Fe1024;
20+
use crate::Fe32;
1121

1222
/// Trait defining a particular checksum.
1323
///
@@ -49,7 +59,7 @@ pub trait Checksum {
4959
///
5060
/// These cannot be usefully pre-computed because of Rust's limited constfn support
5161
/// as of 1.67, so they must be specified manually for each checksum. To check the
52-
/// values for consistency, run `Self::sanity_check()`.
62+
/// values for consistency, run [`Self::sanity_check`].
5363
const GENERATOR_SH: [Self::MidstateRepr; 5];
5464

5565
/// The residue, modulo the generator polynomial, that a valid codeword will have.
@@ -83,6 +93,184 @@ pub trait Checksum {
8393
}
8494
}
8595

96+
/// Given a polynomial representation for your generator polynomial and your
97+
/// target residue, outputs a `impl Checksum` block.
98+
///
99+
/// You must specify an extension field. You should try [`crate::Fe1024`], and if
100+
/// you get an error about a polynomial not splitting, try [`crate::Fe32768`].
101+
///
102+
/// Used like
103+
///
104+
/// ```
105+
/// # #[cfg(feature = "alloc")] {
106+
/// use core::convert::TryFrom;
107+
///
108+
/// use bech32::{Fe32, Fe1024, PrintImpl};
109+
/// use bech32::primitives::checksum::PackedFe32;
110+
///
111+
/// // In codes specified in BIPs, the code generator polynomial and residue
112+
/// // are often only given indirectly, in the reference code which encodes
113+
/// // it in a packed form (shifted multiple times).
114+
/// //
115+
/// // For example in the BIP173 Python reference code you will see an array
116+
/// // called `generator` whose first entry is 0x3b6a57b2. This first entry
117+
/// // is the generator polynomial in packed form.
118+
/// //
119+
/// // To get the expanded polynomial form you can use `u128::unpack` like so:
120+
/// let unpacked_poly = (0..6)
121+
/// .rev() // Note .rev() to convert from BE integer literal to LE polynomial!
122+
/// .map(|i| 0x3b6a57b2u128.unpack(i))
123+
/// .map(|u| Fe32::try_from(u).unwrap())
124+
/// .collect::<Vec<_>>();
125+
/// let unpacked_residue = (0..6)
126+
/// .rev()
127+
/// .map(|i| 0x1u128.unpack(i))
128+
/// .map(|u| Fe32::try_from(u).unwrap())
129+
/// .collect::<Vec<_>>();
130+
/// println!(
131+
/// "{}",
132+
/// PrintImpl::<Fe1024>::new(
133+
/// "Bech32",
134+
/// &unpacked_poly,
135+
/// &unpacked_residue,
136+
/// ),
137+
/// );
138+
/// # }
139+
/// ```
140+
///
141+
/// The awkward API is to allow this type to be used in the widest set of
142+
/// circumstances, including in nostd settings. (However, the underlying
143+
/// polynomial math requires the `alloc` feature.)
144+
///
145+
/// Both polynomial representations should be in little-endian order, so that
146+
/// the coefficient of x^i appears in the ith slot. The generator polynomial
147+
/// should be a monic polynomial but you should not include the monic term,
148+
/// so that both `generator` and `target` are arrays of the same length.
149+
///
150+
/// **This function should never need to be called by users, but will be helpful
151+
/// for developers.**
152+
///
153+
/// In general, when defining a checksum, it is best to call this method (and
154+
/// to add a unit test that calls [`Checksum::sanity_check`] rather than trying
155+
/// to compute the values yourself. The reason is that the specific values
156+
/// used depend on the representation of extension fields, which may differ
157+
/// between implementations (and between specifications) of your BCH code.
158+
#[cfg(feature = "alloc")]
159+
pub struct PrintImpl<'a, ExtField = Fe1024> {
160+
name: &'a str,
161+
generator: &'a [Fe32],
162+
target: &'a [Fe32],
163+
bit_len: usize,
164+
hex_width: usize,
165+
midstate_repr: &'static str,
166+
phantom: PhantomData<ExtField>,
167+
}
168+
169+
#[cfg(feature = "alloc")]
170+
impl<'a, ExtField> PrintImpl<'a, ExtField> {
171+
/// Constructor for an object to print an impl-block for the [`Checksum`] trait.
172+
///
173+
/// # Panics
174+
///
175+
/// Panics if any of the input values fail various sanity checks.
176+
pub fn new(name: &'a str, generator: &'a [Fe32], target: &'a [Fe32]) -> Self {
177+
// Sanity checks.
178+
assert_ne!(name.len(), 0, "type name cannot be the empty string",);
179+
assert_ne!(
180+
generator.len(),
181+
0,
182+
"generator polynomial cannot be the empty string (constant 1)"
183+
);
184+
assert_ne!(target.len(), 0, "target residue cannot be the empty string");
185+
if generator.len() != target.len() {
186+
let hint = if generator.len() == target.len() + 1 {
187+
" (you should not include the monic term of the generator polynomial"
188+
} else if generator.len() > target.len() {
189+
" (you may need to zero-pad your target residue)"
190+
} else {
191+
""
192+
};
193+
panic!(
194+
"Generator length {} does not match target residue length {}{}",
195+
generator.len(),
196+
target.len(),
197+
hint
198+
);
199+
}
200+
201+
let bit_len = 5 * target.len();
202+
let (hex_width, midstate_repr) = if bit_len <= 32 {
203+
(8, "u32")
204+
} else if bit_len <= 64 {
205+
(16, "u64")
206+
} else if bit_len <= 128 {
207+
(32, "u128")
208+
} else {
209+
panic!("Generator length {} cannot exceed 25, as we cannot represent it by packing bits into a Rust numeric type", generator.len());
210+
};
211+
// End sanity checks.
212+
PrintImpl {
213+
name,
214+
generator,
215+
target,
216+
bit_len,
217+
hex_width,
218+
midstate_repr,
219+
phantom: PhantomData,
220+
}
221+
}
222+
}
223+
224+
#[cfg(feature = "alloc")]
225+
impl<'a, ExtField> fmt::Display for PrintImpl<'a, ExtField>
226+
where
227+
ExtField: super::ExtensionField + From<Fe32>,
228+
{
229+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230+
// Generator polynomial as a polynomial over GF1024
231+
let gen_poly = {
232+
let mut v = Vec::with_capacity(self.generator.len() + 1);
233+
v.push(ExtField::ONE);
234+
v.extend(self.generator.iter().cloned().map(ExtField::from));
235+
Polynomial::new(v)
236+
};
237+
let (_gen, length, _exponents) = gen_poly.bch_generator_primitive_element();
238+
239+
write!(f, "// Code block generated by Checksum::print_impl polynomial ")?;
240+
for fe in self.generator {
241+
write!(f, "{}", fe)?;
242+
}
243+
write!(f, " target ")?;
244+
for fe in self.target {
245+
write!(f, "{}", fe)?;
246+
}
247+
f.write_str("\n")?;
248+
writeln!(f, "impl Checksum for {} {{", self.name)?;
249+
writeln!(
250+
f,
251+
" type MidstateRepr = {}; // checksum packs into {} bits",
252+
self.midstate_repr, self.bit_len
253+
)?;
254+
writeln!(f, " const CODE_LENGTH: usize = {};", length)?;
255+
writeln!(f, " const CHECKSUM_LENGTH: usize = {};", gen_poly.degree())?;
256+
writeln!(f, " const GENERATOR_SH: [{}; 5] = [", self.midstate_repr)?;
257+
let mut gen5 = self.generator.to_vec();
258+
for _ in 0..5 {
259+
let gen_packed = u128::pack(gen5.iter().copied().map(From::from));
260+
writeln!(f, " 0x{:0width$x},", gen_packed, width = self.hex_width)?;
261+
gen5.iter_mut().for_each(|x| *x *= Fe32::Z);
262+
}
263+
writeln!(f, " ];")?;
264+
writeln!(
265+
f,
266+
" const TARGET_RESIDUE: {} = {:?};",
267+
self.midstate_repr,
268+
u128::pack(self.target.iter().copied().map(From::from))
269+
)?;
270+
f.write_str("}")
271+
}
272+
}
273+
86274
/// A checksum engine, which can be used to compute or verify a checksum.
87275
///
88276
/// Use this to verify a checksum, feed it the data to be checksummed using
@@ -311,6 +499,9 @@ impl<'hrp> Iterator for HrpFe32Iter<'hrp> {
311499

312500
#[cfg(test)]
313501
mod tests {
502+
#[cfg(feature = "alloc")]
503+
use core::convert::TryFrom;
504+
314505
use super::*;
315506

316507
#[test]
@@ -327,4 +518,42 @@ mod tests {
327518
assert_eq!(packed.unpack(2), 2);
328519
assert_eq!(packed.unpack(3), 1);
329520
}
521+
522+
#[test]
523+
#[cfg(feature = "alloc")]
524+
fn bech32() {
525+
// In codes that Pieter specifies typically the generator polynomial is
526+
// only given indirectly, in the reference code which encodes it in a
527+
// packed form (shifted multiple times).
528+
//
529+
// For example in the BIP173 Python reference code you will see an array
530+
// called `generator` whose first entry is 0x3b6a57b2. This first entry
531+
// is the generator polynomial in packed form.
532+
//
533+
// To get the expanded polynomial form you can use `u128::unpack` like so:
534+
let unpacked_poly = (0..6)
535+
.rev() // Note .rev() to convert from BE integer literal to LE polynomial!
536+
.map(|i| 0x3b6a57b2u128.unpack(i))
537+
.map(|u| Fe32::try_from(u).unwrap())
538+
.collect::<Vec<_>>();
539+
assert_eq!(unpacked_poly, [Fe32::A, Fe32::K, Fe32::_5, Fe32::_4, Fe32::A, Fe32::J],);
540+
// To get a version of the above with bech32 chars instead of Fe32s, which
541+
// can be a bit hard to print, just stick a `.map(Fe32::to_char)` into the
542+
// above iterator chain.
543+
544+
// Ok, exposition over. The actual unit test follows.
545+
546+
// Run with -- --nocapture to see the output of this. This unit test
547+
// does not check the exact output because it is not deterministic,
548+
// and cannot check the code semantics because Rust does not have
549+
// any sort of `eval`, but you can manually check the output works.
550+
let _s = PrintImpl::<Fe1024>::new(
551+
"Bech32",
552+
&[Fe32::A, Fe32::K, Fe32::_5, Fe32::_4, Fe32::A, Fe32::J],
553+
&[Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::P],
554+
)
555+
.to_string();
556+
#[cfg(feature = "std")]
557+
println!("{}", _s);
558+
}
330559
}

0 commit comments

Comments
 (0)